iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 23
0
Modern Web

React - DOM界的彼方系列 第 23

Day 23: React篇 - TodoApp程式 + Fetch/Ajax 於生命週期方法

intro

今天的主題是要加入與伺服器資料互動的功能,一般我們所說的在網頁上的JavaScript應用與伺服器溝通,指的就是使用Ajax技術,傳送資料到伺服器或是與伺服器要資料。我們的範例程式主要會用在一開始載入資料、勾選為已完成的項目時,以及編輯現有的項目時這三個情況。

這個程式最後的呈現結果,就像下面的動態圖片這樣,重點是在於它會傳出與外部伺服器溝通的資料:

TodoApp元件展示

註: 本文章同步放置於Github庫的這裡,所有的程式碼也在裡面。

在解說之前,本篇使用的一些額外的工具與知識,在這篇文章不會再多作說明,這個就你需要進一步學習了,下面有附它們的連結:

  • json-server: 這是一個架在Node.js上的用於測試JSON格式資料的伺服器,而且是REST API,裡面用了lowdb,所以其實它是一個具有資料庫寫入、讀取、查詢的功能的伺服器。這個工具在Github上有1萬8千個星,如果你要開發像我們這種JavaScript應用,而且是用json交換資料的應用,學會用這個工具,保証讓你開發的日子輕鬆很多。

  • Postman: 這工具則是一個客戶端程式,也有Chrome的外掛擴充版本,這是用來模擬由客戶端發送各種要求(Request)用的,像POST、GET、PUT...等等。也就是例如在還沒開始寫程式前,先作測試,看你的資料傳到伺服器(json-server)上能不能正確查詢到資料,或是新增資料等等。當然它也有很多其他的功能,不過我只用這部份而已。

  • Fetch API: 這是一個API,是HTML5標準中的新特性,當然它是一個實作在JavaScript的新API,Fetch API是一個完全以為基礎的API,Fetch並不是一個單純的XHR擴充加強版或改進版本,它是一個用不同角度思考的設計,雖然是可以作類似的事情。此外,Fetch還是基於Promise語法結構的,而且它的設計足夠低階,這表示它可以依照實際需求進行更多彈性設定。相對於XHR的功能來說,Fetch已經有足夠的相對功能來取代它,但Fetch並不僅於此,它還提供更多有效率與更多擴充性的作法。

上面都是這個範例程式中有用到的部份,這些內容如果要整個解說完,可能光說這些30天都不夠用,我附的連結中的資料就請再自行學習了。

程式碼說明

本次的程式碼只有修改到App元件,其他都不變,這也是因為App元件裡有所有這個範例應用中所有的狀態,更動狀態的所有方法也在這裡面,所以與伺服器溝通就靠App元件就行了。

我們在App元件加了一個新方法handleServerItemsLoad,它會載入所有記錄在現在伺服器的項目資料。使用的是fetch的Promise語法,最後在正確載到資料時,作this.setState,但因為伺服器資料上並沒有isEditing,所以會在setState前作資料的整理,加上isEditing欄位。另外排序用id值,實際上id值就是我們每個項目加入的時間(微秒值)。程式碼如下:

handleServerItemsLoad = () => {
  fetch('http://localhost:5555/items?_sort=id&_order=DESC', {
    method: 'GET'
    })
    .then((response) => {
      //ok 代表狀態碼在範圍 200-299
      if (!response.ok) throw new Error(response.statusText)
      return response.json()
    })
    .then((itemList) => {

       //加入{ isEditing: false }屬性
       const items = itemList.map((item) => {
         return Object.assign({}, item, { isEditing: false })
       })

      //載入資料,重新渲染
      this.setState({
        items,
      })
    })
    .catch((error) => {
      //這裡可以顯示一些訊息
      //console.error(error)
    })
}

然後在生命週期方法的呼叫這個方法,componentDidMount在ES6類別的元件語法中,是內建就有的一個方法,它會在React元件"已經"掛載到真實網頁上執行裡面的程式碼。程式碼如下:

componentDidMount() {
  // 元件"已經"載入,所以可以載入資料進來
  this.handleServerItemsLoad()
}

註: 無狀態元件,也就是用函式語法寫的元件,是"不能"使用生命週期方法的。

另一個新增的方法稱為handleServerItemAdd,它是用於在新增項目用的,REST API用的是POST這個method值,因為我們在資料庫裡不需要isEditing這個屬性,所以也有先作資料的整理工作。程式碼如下:

handleServerItemAdd = (aItem: Item) => {
  //處理payload,不需要isEditing欄位
  const { id, title, isCompleted } = aItem
  const payload = { id, title, isCompleted }

  //作POST
  fetch('http://localhost:5555/items', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
   })
    .then((response) => {
      //ok 代表狀態碼在範圍 200-299
      if (!response.ok) throw new Error(response.statusText)
      return response.json()
    })
    .then((item) => {
      //這裡可以顯示一些訊息,或是結束指示動畫…
      //console.log(item)
    })
    .catch((error) => {
      //這裡可以顯示一些訊息
      //console.error(error)
    })
}

在原先的handleItemAdd中加上呼叫這個handleServerItemAdd方法,讓資料同時也會傳到伺服器上儲存:

//加入新資料到資料庫
this.handleServerItemAdd(aItem)

最後是這個handleServerItemUpdate,用於資料更新用的,REST API用的是PUT這個method值。大致上與上面的handleServerItemAdd方法都一樣,差異只是你需要給定要更新哪一筆資料,所以要給id值,這裡用了樣版字串的語法,像http://localhost:5555/items/${id}這樣的寫法,它也很方便使用,可以組合變數與字串。程式程如下:

handleServerItemUpdate = (aItem: Item) => {
  //處理payload,不需要isEditing欄位
  const { id, title, isCompleted } = aItem
  const payload = { id, title, isCompleted }

  //作POST
  fetch(`http://localhost:5555/items/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
   })
    .then((response) => {
      //ok 代表狀態碼在範圍 200-299
      if (!response.ok) throw new Error(response.statusText)
      return response.json()
    })
    .then((item) => {
      //這裡可以顯示一些訊息,或是結束指示動畫…
      //console.log(item)
    })
    .catch((error) => {
      //console.error(error)
    })
}

當然,你也要在更新項目狀態用的handleStylingItem來呼叫這個方法,讓項目完成狀態改變時,伺服器上的資料也一併會更新:

//更新某一筆資料
this.handleServerItemUpdate(newItems[index])

以上就是程式碼的所有更動說明。當然它並會太複雜,有些動態指示的處理,或是錯誤時要如何處理,這留給你可以再去寫了。


生命週期方法

在React元件的設計中,因為它是個虛擬DOM的概念,所以每個元件實際上都是有生命週期(Lifecycle)的設計,也就是一個元件從一開始掛載(Mounting)到網頁上,然後呈現在網頁上,在網頁上因為事件的觸發進行更新(Updating)或重新渲染(being re-rendered),最後從網頁上消失,稱為卸載(Unmounting)。

React為這些生命週期的階段,設計了一些方法讓開發者使用,在某個生命週期階段你可以利用這些方法,在裡面執行一些東西,實際上有兩個生命週期方法我們已經看過,也用到了,以下列出這些生命週期方法:

卸載(Unmounting)

  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount()

更新(Updating)

  • componentWillReceiveProps()
  • shouldComponentUpdate()
  • componentWillUpdate()
  • render()
  • componentDidUpdate()

卸載(Unmounting)

  • componentWillUnmount()

其他的重要方法還有兩個,分別是setState()forceUpdate()

生命週期方法在要作某些工作時特別重要,例如說一開始載入資料(Fetch/Ajax)或是計時器(Timer),或是自訂的事件監聽等等。因為你不能在元件還沒呈現在網頁上作這些事,所以只能在componentDidMount中這些事情。計時器的clearTimeout或clearInterval,或是自訂事件監聽的取消則是在componentWillUnmount方法中作這些事情。

其實這個設計還滿簡單的,我後來有畫幾張圖來解說這些生命週期的階段,因為網路上找到的有些是亂畫或是畫得不清楚,有些是很舊的不太符合較新版本的React。

mounting

updating

unmounting

生命週期方法其實不需要太去記,主要是componentDidMountcomponentWillUnmount這兩個,搭配計時器與一開始載入資料使用,大概一開始學React會容易看到的。當然componentWillReceiveProps有可能也會在很多元件交互應用時看到。


json-server

這裡說明一些json-server的簡單用法。

首先當然是安裝它,直接用在命令列工具(終端機)中用npm工具就可以安裝,裝在全域即可:

npm install -g json-server

要啟動它也是用命令列工具(終端機)的指令,當然前提是你要先建立一個db.json檔案,這個檔案中在我們上面這個範例中有附上:

json-server --watch --port 5555 db.json

這個指令代表要啟動一個在埠號為5555的json-server伺服器,之後就可以用瀏覽器打開"http://localhost:5555/items"

註: 我們開發測試React應用的是3000埠,json-server伺服器是5555埠,相當於在同一個電腦啟動了兩個不同的伺服器。

註: 上面的5555是隨便取的埠號,你要用8888或9999也可以,不過埠號有一定的範圍就是。

db.json檔案格式就類似像下面這樣,它與JS中的物件格式很像,但它是個純文字檔而已。json-server伺服器的網址後加上items會自動只列出items裡面的json資料,它稱之為Routes(路由):

{
  "items": [
    {
      "id": 1482513391121,
      "title": "聽演唱會",
      "isCompleted": true
    }
  ]
}

那麼,因為json-server伺服器是個用REST API的伺服器,而且又有小型資料庫,又該如何新增、讀取、更新…裡面的資料?

首先你要先理解REST是什麼,也就是在傳資料時的method各自分別要作不同的事情之用,例如下面幾個:

  • POST = 新增
  • GET = 讀取
  • PUT = 更新
  • DELETE = 刪除
  • PATCH = 取代部份資料

下面就幾種常用的要作某些事情的範例:

載入所有資料

載入所有資料,預設是用id由小至大(ASC)排序:

GET /items

用id排序,由大至小:

GET /items?_sort=id&_order=DESC

新增一筆資料

POST /items

資料範例:

{
  "id": 4,
  "title": "44444",
  "isCompleted": false
}

更新一筆資料

PUT /items/4

資料範例:

{
  "id": 4,
  "title": "4321",
  "isCompleted": false
}

刪除一筆資料

DELETE /items/4

結論

本章主要是展示了React元件在與外部伺服器的溝通交互應用時,要如何來寫這些功能。這個範例程式用了新的Fetch API,以及幾個常常用在開發這類JS應用時會用到的工具,或許你可能還不熟悉,但我會建議你可以學習,因為這些新式的API,雖然它們並不是ES6標準的東西,但也是現在很重要的、很常被使用的一些API。

另一個主題當然是React中的生命週期方法,其實這也不是很困難的一個主題,主要要你要知道它是有這設計的,在某些特定的時刻,你非得使用這些生命週期方法來作某些事情。而在範例中的state與資料庫的資料是故意設計成不一樣的,因為state中的資料有可能不相等於資料庫的資料,這一點你需要很清楚,state是應用程式整體的狀態資料,它仍然有暫時存放的特性,在你重新載入網頁時,state就回復到最初始的狀態,只有依靠像資料庫的永久儲存性的方式,才能真正保存資料。


上一篇
Day 22: React篇: TodoApp程式 + 搜尋/過濾 + 排序
下一篇
Day 24: React篇 - 路由器(router) 與 本篇小結
系列文
React - DOM界的彼方30

尚未有邦友留言

立即登入留言