# React(MRWR)第 7 節: Data Persistence with API Requests > Udemy課程:[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/) `20230825Fri.~20230828Mon.` :::danger 7-121 rest client介紹及使用 7-124 useEffect() ::: ## 7-115. Adding Data Persistence 第7節將延續第6節完成的專案去做修改,第6節中當我們按下F5後,網頁一刷新,之前建立的清單也將跟著消失。(如下圖所示) ![](https://hackmd.io/_uploads/HyxQTRS63.png) 而第7節將透過API來解決這個問題。 ![](https://hackmd.io/_uploads/B1FHaRS63.png) 不過關於API server,我們在這裡並不會從頭來建置它,我們會直接使用現有的JSON server ![](https://hackmd.io/_uploads/BJNBCCB62.png) 他整個運作流程就像是這個樣子: ![](https://hackmd.io/_uploads/HJSDvyUph.png) 資料都會儲存在API server中,等著我們發送request,等API server收到request後,他就會回傳response給我們,而我們就可以取得books中的data。 而關於原先的三個function也會有所變化,原先是對位於`App.js`中的books state做變化,而現在將要利用發送request的方式,對於位在API server中的data做改變。 而當API server中的data改變之後,就會回傳response告知已刪除,該function才會對`App.js`中的books state做更動。 舉例來說,deleteBookById發送刪除data的request給API server,之後收到API server回傳已刪除的回覆,deleteBookById就會對`App.js`中的books state刪除其中的data。 ![](https://hackmd.io/_uploads/BJNcdkITh.png) 因此這章節我們要做的就是以下三件事情: ![](https://hackmd.io/_uploads/rkvQq18p2.png) **** ## 7-116. Server Setup 再來要建立JSON server,老師將它大致分成了四個步驟: ![](https://hackmd.io/_uploads/HJu99JIan.png) <div style=" width: fit-content; font-size: 0px; margin: 10px 0;"> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #c7d6ed; font-size: 18px"> Step 1. </p> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #e4e9f2; font-size: 18px"> install JSON server with NPM at the terminal </p> </div> 以terminal打開同一個專案,並打上: ```javascript! npm install json-server ``` ![](https://hackmd.io/_uploads/ryCxnyIT3.png) <div style=" width: fit-content; font-size: 0px; margin: 10px 0;"> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #c7d6ed; font-size: 18px"> Step 2. </p> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #e4e9f2; font-size: 18px"> Create a "db.json" file. This is where data will be stored </p> </div> 先建立一個檔案叫做"db.json"在該專案資料夾中。 ![](https://hackmd.io/_uploads/HywO2J863.png) 並在這個檔案先寫上一些code: ![](https://hackmd.io/_uploads/H1g1ayUTh.png) <div style=" width: fit-content; font-size: 0px; margin: 10px 0;"> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #c7d6ed; font-size: 18px"> Step 3. </p> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #e4e9f2; font-size: 18px"> Create a commandto run JSON-Server </p> </div> 先打開`package.json`,並找到以下內容: ![](https://hackmd.io/_uploads/ryQXZxLa2.png) 接著,我們要對scripts做點修改,我們增加一行內容: ```javascript! "server": "json-server -p 3001 --watch db.json", ``` ![](https://hackmd.io/_uploads/ryLkGgUp2.png) <div style=" width: fit-content; font-size: 0px; margin: 10px 0;"> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #c7d6ed; font-size: 18px"> Step 4. </p> <p style="display: inline-block; border:black solid 1px; margin: 0px; padding: 10px; background-color: #e4e9f2; font-size: 18px"> Run the command! </p> </div> 接著最後一個步驟,我們要來執行剛剛建立的command。而執行方法與我們在打開react專案時輸入的`npm run start`相似。 這裡我們透過terminal打開這個章節的專案,並在terminal內打上: ```javascript! npm run server ``` 輸入並執行後,可以看見以下的畫面: ![](https://hackmd.io/_uploads/SyWAMeLa2.png) 到這裡,我們就建立完成初步的設置了! **** ## 7-117. What Just Happened? 我們可以知道我們利用了兩個command(命令)來使這個專案運行,分別是`npm run start`跟`npm run server`。 也就是說,當我們想要使用這個專案、想要打開這個專案的時候,就必定需要運行這兩個command。 ![](https://hackmd.io/_uploads/BJymdgLa3.jpg) 我們還可以從上圖得知這兩個command執行後的server,各自佔據了不同的ports,react dev server佔據了3000 port,而JSON server則佔據了3001 port。 ![](https://hackmd.io/_uploads/BkteqeL6h.jpg) 而一個port只能給一個server使用,若重複則會報錯。react dev server佔據3000 port一般來說是預設,而JSON server佔據3001 port則是我們先前設置的,因此也不一定要3001,只要不是3000都可以,至於怎麼設置可以看回專案中的`package.json`我們當初的設定: ![](https://hackmd.io/_uploads/ryLkGgUp2.png) 而我們要在程式中做的事情就是發送request到3001 port,示意圖如下所示: ![](https://hackmd.io/_uploads/rJ-gseIa2.png) 接著老師快速的大致說明以上內容的細節。 首先是`package.json`中的設定,這個設定讓我們得以在跑`npm run server`時,執行"json-server -p 3001 --watch db.json"這串命令。 ![](https://hackmd.io/_uploads/HkguieLp3.png) 1. **-p 3001** 可以改變server監聽的port。以這裡為例,即監聽3001 port。 2. **--watch db.json** 告訴server要將資料儲存在`db.json`這個檔案裡面。 ![](https://hackmd.io/_uploads/ByK-3gIp2.png) **** ## 7-118. How the API Works 回到一開始建立的db.json檔案,當時我們在檔案中加入了以下的內容,而這段程式碼代表著什麼呢? ```javascript! { "books": [] } ``` ![](https://hackmd.io/_uploads/r14kCBva3.png) 1. db.json檔案將會儲存我們的data 2. 存入key為books的陣列之中 3. 我們會在陣列中放入所有不同的book物件 JSON server可以對資料做許多動作,不過這裡主要談論以下四種: ![](https://hackmd.io/_uploads/HyhEdmuah.png) ![](https://hackmd.io/_uploads/HkeUOXuTn.jpg) ![](https://hackmd.io/_uploads/r1gIdm_T3.jpg) ![](https://hackmd.io/_uploads/H1e8O7d63.jpg) ![](https://hackmd.io/_uploads/BybU_QOTh.jpg) **** ## 7-119. Introducing the REST Client 老師說再來我們要用一個叫做"standalone API client"的東西來看看API是否可以如我們預期的運作。 ![](https://hackmd.io/_uploads/r1d9iQ_T3.png) Step1 ![](https://hackmd.io/_uploads/B1omONOpn.jpg) Step2 ![](https://hackmd.io/_uploads/BkxiXu4dTn.jpg) 可參考這篇文章:[[vscode] Rest Client](https://pjchender.dev/other/vscode-rest-client/) 老師提到會用這個rest client的原因是,之後打開這個專案的人,只要打開副檔名為.http(以下圖為例即api.http這個檔案),就可以知道如何使用這份API(例如如何新增book、修改book......)。 ![](https://hackmd.io/_uploads/BJVstVdT2.png) **** ## 7-121. Using the REST Client 老實說,還是不太懂rest api在幹麻,所以就是先照著老師做,或許哪天就會開竅了。 1. 先在專案中建立一個檔案叫做`api.http`(有看到說副檔名可以叫`.http`或`.rest`都可以的) 2. 打開`api.http`,並打入以下內容: ```javascript! GET http://localhost:3001/books HTTP/1.1 Content-Type: application/json ``` 打上這些內容,剛剛安裝的rest client套件就會知道我們現在想要試著發送request,而我們可以看到打上這些內容後,最上方會出現一個`Send Request`的字串可以點擊。 ![](https://hackmd.io/_uploads/H1Gm0NOpn.jpg) 3. 這裡我們再多做幾個request,而多個request之間我們可以利用三個井字號(`###`)來做分隔。 ```javascript! GET http://localhost:3001/books HTTP/1.1 Content-Type: application/json ### 第二個request... ``` 4. 再來建立第二個request request用法可參考7-118中的method,如下圖: ![](https://hackmd.io/_uploads/HyhEdmuah.png) 這裡將使用POST,來建立book。 因為是使用POST,要記得下方還要空一行後,加上要建立的自料內容。 ![](https://hackmd.io/_uploads/HkeUOXuTn.jpg) ```javascript! POST http://localhost:3001/books HTTP/1.1 Content-Type: application/json { "title": "Harry Potter" } ``` 一樣再寫好內容之後,按下`Send Request`,可以看到右側多了剛剛新增的物件了!而且會發現該新增的物件,也自動的獲得了id。 ![](https://hackmd.io/_uploads/BJIMZruTn.jpg) 且此時打開之前建立的`db.json`,之前原先只有一個空陣列,現在他自動加入了我們剛才透過發送API請求而建立的物件:(下圖是由rest client操作,並非我手動加上去的,超酷欸!) ![](https://hackmd.io/_uploads/HkCYbBdTn.png) 5. 最後兩個課程中會提到的method:PUT、DELETE分別這樣輸入: ```javascript! PUT http://localhost:3001/books/1 HTTP/1.1 Content-Type: application/json { "title": "Darl Tower" } ``` ![](https://hackmd.io/_uploads/BkL9QHua3.png) ```javascript! DELETE http://localhost:3001/books/1 HTTP/1.1 Content-Type: application/json ``` ![](https://hackmd.io/_uploads/S1cc7Buan.png) 最後來個總結(可以用#作為註解用),這個章節所用的rest client,並不是必要的東西,但可以讓我們更理解API的操作。 ```javascript! # Get all books GET http://localhost:3001/books HTTP/1.1 Content-Type: application/json ### # Create a book POST http://localhost:3001/books HTTP/1.1 Content-Type: application/json { "title": "Harry Potter" } ### # Edit a book PUT http://localhost:3001/books/1 HTTP/1.1 Content-Type: application/json { "title": "Darl Tower" } ### # Delete a book DELETE http://localhost:3001/books/1 HTTP/1.1 Content-Type: application/jso ``` **** ## 7-122. Creating a New Record 老師一開始便提到,我們不會直接利用React去做request,因為React的工作主要就是把內容呈現到畫面上,所以為了可以達成製作request的目的,我們這裡會利用第三方函式庫Axios來完成。 當然使用函式庫的第一步就是要來下載進我們的專案啦!透過terminal打開專案,然後輸入以下內容: ```javascript! npm install axios ``` ![](https://hackmd.io/_uploads/ryjFUSOa2.png) 下載好之後,將axios import進專案,就可以來動工了。 **App.js** ```javascript! import axios from 'axios'; ``` 先打開`App.js`檔案,我們首先看到`createBook()`這個function。下圖為先前寫得code,id是直接用隨機生成的方式。 ![](https://hackmd.io/_uploads/ByTawHOa2.png) 這個function需要處理兩件事情,一個是發送request到JSON server,一個是取得response,這個response會包含新create的book,而我們要將這個response給`setBooks()`,讓它去更新books state。 那發送request的方式,便會透過Axios來達成。 這裡我們要create book,所以用得method叫做`POST`。忘記的話記得會去參考7-118中的method,如下圖: ![](https://hackmd.io/_uploads/HyhEdmuah.png) axios的語法則如下,使用POST method之後,可以傳入兩個參數,第一個參數放入連結、第二個參數可以放入一些配置,例如這裡我們將放入request的body(此處即為要建立的book物件) 另外,可以複習[5-65. [Optional] Using Async:Await](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?view#5-65-Optional-Using-AsyncAwait),當我們要發送request時,要記得加上async/await,來告訴JS要等待回傳response後,才可以繼續往下執行。 至於async/await更詳細內容,可以參考[MDN async function](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function),裡面的描述有提到更詳習的內容(即JS是如何及為何去等待的),會牽扯到promise。 ![](https://hackmd.io/_uploads/SJWTNL_ph.png) ```javascript! const createBook = async (title) => { const response = await axios.post("http://localhost:3001/books",{ title }) console.log(response); } ``` 當我們在input中輸入內容後,按下click鍵,可以看到console印出了一個物件,而這個物件便是由JSON server幫我們儲存的: ![](https://hackmd.io/_uploads/S1nZOLd62.jpg) 也能在Network的地方,看見我們發送的請求。 ![](https://hackmd.io/_uploads/rJ2b_Lda3.jpg) 而我們也可以打開`db.json`檔案,確認剛剛輸入的內容是否有儲存到裡頭的陣列中。 ![](https://hackmd.io/_uploads/B1pF_IOT3.png) 我們從上方驗證可知response為何物,現在我們不要再console.log(response)了,我們要把這個response丟給setBooks(),讓他去幫我們更新books state。 只是在丟給setBooks()前,我們這個response是指單一個新建的資料,但是過去在books中的資料也要一併保留,所以會用到展開運算子。 **App.js** ```javascript! const createBook = async (title) => { const response = await axios.post("http://localhost:3001/books",{ title }) const updatedBooks = [ ...books, response.data ] setBooks(updatedBooks); } ``` 但我們會發現此時如果重整整個網站的話,資料並不會保存,這是因為我們在載入這個網頁時,我們應該要跟API發送請求,請API回傳給我們目前的整個books清單。(下一章節處理) **** ## 7-123. Fetching a List of Records 再來我們要讓這個網頁一載入,就把books裡面的所有內容都渲染上去,所以我們要在一開始就抓取整個`db.json`中books陣列裡的所有資料。 至於怎麼抓取?運用axios的method GET,便能透過JSON server取得所有的books裡的資料。 打開`App.js`,在App元件中新增一個function叫做`fetchBooks()`。 在複習一次: 1. 為了要讓JS等待response回傳,要用到async/await。 2. 操作API將透過axios這個第三方函式庫。 3. axios的method第一個參數放入網址,這個網址即我們要發送request的目的地。 4. response為一物件,我們只需要他的data而已,所以取response.data。 **App.js** ```javascript! const fetchBooks = async () => { const response = await axios.get("http://localhost:3001/books"); setBooks(response.data); } ``` 建立好這個function之後,問題就來了,我們要再哪裡呼叫這個function?fetchBooks 這個function我們只希望在載入網頁時執行那麼一次就好,所以他該放在哪呢? 這將在下一章節處理。 **** ## 7-124. Introducing useEffect 前面提到`fetchBooks()` function我們只希望在我們載入網頁時使用而已,而這裡將使用新的React語法叫做"useEffect"! ![](https://hackmd.io/_uploads/rkZwWD_T2.png) 第一個參數我們會放入function,而該function包含我們想執行的code,課程在實作上function基本上都是使用arrow function(箭頭函式)。 第二個參數可以不放也可以放一個陣列,老師說後面會解釋。 總之,我們希望只在第一次執行fetchBooks的話,那麼就寫出以下程式: ```javascript! import { useEffect } from "react"; useEffect(() => { fetchBooks(); },[]); ``` **** ## 7-125. useEffect in Action useEffect有一些需要注意的事項: ![](https://hackmd.io/_uploads/S1nmydd63.png) useEffect的用法如下圖所示: ![](https://hackmd.io/_uploads/ByHHqnY62.jpg) 因此我們可以從上圖得知,useEffect第一個參數是一個function,這個function永遠會在第一次渲染時執行,而第二次、第三次...渲染,則不一定會執行,至於除了第一次以外的渲染,這個function究竟要不要執行,這取決於第二個參數。 而第二個參數如何影響?老師整理了一張圖: ![](https://hackmd.io/_uploads/S1mMihKp3.png) 最右側的"counter"只是個舉例,他可以是任何的prop或是state。 **** ## 7-126. More on useEffect > codepen: https://codepen.io/sgrider/pen/BarEowz?editors=0011