# React(MRWR)第 5 節: Using an API with React #React(MRWR)第 5 節: Using an API with React > Udemy課程:[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/) `20230802Wed.~` :::danger 5-46 用文件查詢想獲取的API之url 5-64 axios 5-65 async、await 5-70 form預設的問題 5-71 抓取input裡資料的禁忌、方法 5-71 onSubmit、onChange都是JS中的event handler ::: ## 5-60. Project Setup 第5節專案要完成的內容如下圖,使用者可以在搜尋欄位打上關鍵字,透過API向第三方發送請求,並在底下顯示關鍵字的相關圖片們。 這次會建立4個component: 1. parent component: * `App`:父層,最主要的檔案,用來顯示所有子層元件 2. children component: * `SearchBar`:搜尋欄位 * `ImageList`: 用來處理所有的單張圖片,亦即用來放置`ImageShow` 元件,其為`ImageShow`的父層元件 * `ImageShow`:單張的圖片,其為`ImageList`的子層元件 ![](https://hackmd.io/_uploads/S1XXVuPsn.png) 下圖為所有component之間的關係: ![](https://hackmd.io/_uploads/HkgEvdPj3.png) :::warning 會發現檔案越來越多了,到後面的專案可能也會越來越複雜,若全部檔案都丟在`src`資料夾裡面,會變得相當難管理。所以我們在`src`資料夾中新增另一個`components`資料夾,將component放進該資料夾裡。 以此節專案為例則更改如下,至於`App.js`,他雖然也是component,一個有點像整個專案的中央,所以`App.js`放置在`components`資料夾裡面或外面都沒問題。 ![](https://hackmd.io/_uploads/H1LFddPin.png) ::: **** ## 5-61. The Path Forward 我們會運用到的API是由unsplash這個平台免費提供。 ![](https://hackmd.io/_uploads/S1ZOnOvon.png) 首先要知道React並沒有任何工具、物件或函式是可以發送HTTP請求的,React只在乎如何顯示內容以及如何處理使用者觸發的事件,但這未必是壞事,因為這代表著我們可以不用理會React去寫很多商業邏輯(business logic)或是獲取(fetching)資料的方法。 **** ## 5-62. Overview of HTTP Requests **HTTP Method** HTTP Request 回傳的資料中含HTTP Method > 參考資料:[HTTP 請求方法(MDN)](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methodshttps://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods) **HTTP Status Code** HTTP Response 回傳的資料中含HTTP Status Code > 參考資料:[HTTP 狀態碼(MDN)](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status) 這裡要注意一點,發出請求後,會等待一小小段時間,才回獲得回應。 ![](https://hackmd.io/_uploads/B1WAAYOo2.jpg) 所以當我們寫以下第一種寫法時,JS並不會等我們獲得response,而會直接接續馬上執行下一行程式。所以我們發出請求後,應該要告訴JS:「等一下!我還沒收到回應,你先等我一回在繼續執行。」,至於是什麼語法,後面章節(5-65)會說明。 ![](https://hackmd.io/_uploads/SyWCCK_oh.jpg) 補充:JS是一種單執行緒的語言,所以他做的事情是一種同步(sync)的表現,如下圖所示。因此如果要達到上述目的,則需要運用到非同步(async)的概念。 ![](https://hackmd.io/_uploads/rJ5excuin.jpg) > 參考資料:[[JavaScript] 一次搞懂同步與非同步的一切:一次做幾件事情 — 同步(Sync)與非同步(Async)](https://medium.com/itsems-frontend/javascript-sync-async-22e75e1ca1dc) **** ## 5-63. Understanding the API 接著要先來註冊API,以及看看本次專案要使用的API的文件。 <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step1.</h3> </div> 首先,來到[Unsplash API頁面](https://unsplash.com/developers) 按下右上角的"Register as a developer"進行註冊。 ![](https://hackmd.io/_uploads/B12F-quj2.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step2. </h3> </div> 註冊登入後,來到"Your apps"的頁面。 ![](https://hackmd.io/_uploads/HkLpfcdo2.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step3. </h3> </div> 來到"Your apps"頁面後,往下滑可以看到一個區塊"New Application",並點擊進入。 ![](https://hackmd.io/_uploads/SyQXm9_i2.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step4.</h3> </div> 以下皆勾選後,按下底下的"Accept terms"。 ![](https://hackmd.io/_uploads/Byr6Xcdjh.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step5. </h3> </div> 填寫資訊,包含名稱和描述。 ![](https://hackmd.io/_uploads/ByRtNqOih.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">step6. </h3> </div> 之後會跳轉至剛剛建立的頁面中。 ![](https://hackmd.io/_uploads/S1dgH5dj2.png) 將該頁面下滑,可以看見區塊"keys"。 ![](https://hackmd.io/_uploads/BJkvw9Oo3.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">來看文件! </h3> </div> * **Location** 點擊上方的"Documentation"。並從左側列表依序進入: shema > location。 ![](https://hackmd.io/_uploads/HkLpfcdo2.png) 我們可以從中看到對於[Location](https://unsplash.com/documentation#location)的敘述,代表著如果我們想使用(access)API,必須發送請求到`https://api.unsplash.com/`該網址中。 ```! The API is available at https://api.unsplash.com/. Responses are sent as JSON. ``` * **Public Authentication** 另外一個值得注意的就是[Public Authentication](https://unsplash.com/documentation#public-authentication),可以從左側列表 Authorization > ublic Authentication取得資訊。 這裡提到當你想使用API,我們必須包含一個特殊的header叫做"Authentication",這個"Authentication"需要有"Client-ID YOUR_ACCESS_KEY"這些資訊。其中,"YOUR_ACCESS_KEY"為先前Step 6.產生的Keys所付資訊。 ```! To authenticate requests in this way, pass your application’s access key via the HTTP Authorization header: Authorization: Client-ID YOUR_ACCESS_KEY ``` * **Search photos** 接著是"Search photos",可以從左側列表 Search > Search photos 找到。 其中附有參數可以參考使用。 ![](https://hackmd.io/_uploads/SyY5C5_oh.png) ``` Get a single page of photo results for a query. GET /search/photos ``` **** ## 5-64. Making an HTTP Request 要製作請求,在前面有提過,並不需要使用到react,一般來說會利用JS的函式庫axios或是fetch,不過老師提到這邊只會教axios(比較好用)。 課程中利用npm來安裝axios。 ```! npm install axios ``` axios 函式庫是個物件,並擁有多個function供我們使用,這些不同的function提供我們不同方法發起 HTTP Request。 例如說我們要使用function 並且傳入我們想獲取API的url作為function的第一個參數,另外第二個參數則放置一些物件(例如headers、params)。範例如下: ```javascript! axios.get(url, { }) ``` :::info 關於第二個參數必要有的內容為header(包含Authorization)、query。 ![](https://hackmd.io/_uploads/S1HfRJ5o2.png) 舉例來說,大致上會長這個樣子。 ![](https://hackmd.io/_uploads/SkFC0yqon.png) ::: 這裡,將創建一個新的檔案叫做"api.js" 1. 在裡面import axios 2. 建立一個`searchImages`的function,並在此function中使用axios 3. 放url到第一個參數,此url即我們想獲取API的url "https://api.unsplash.com/search/photos" ==[註ㄧ]== 4. 接著,第二個參數:==[註二]== ```javascript! headers:{ Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4' }, params:{ query: 'cars' } ``` 5. 加上async、await,並在底部return response變數 這步驟的作用將在下一章節說明。 ```javascript! import axios from "axios"; const searchImages = async () => { const response = await axios.get('https://api.unsplash.com/search/photos', { headers: { Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4' }, params: { query: 'cars' } }) console.log(response); return response; } ``` 6. 加上"export default searchImages" ```javascript! export default searchImages; ``` :::danger [註ㄧ] API的url可以從官方文件查詢(5-63時有提到) 1. **[Location](https://unsplash.com/documentation#location)** The API is available at ```! https://api.unsplash.com/ ``` Responses are sent as JSON. 2. **[Search photos](https://unsplash.com/documentation#search-photos)** Get a single page of photo results for a query. ```! GET /search/photos ``` 綜合以上兩點,可得我們想要獲取API的url為: ```! https://api.unsplash.com/search/photos ``` ::: :::danger [註二] 第二個參數(5-63時有提到) 1. 第一個參數"header" **[Authorization](https://unsplash.com/documentation#public-authentication)** headers放置Authorization,目的是為了告訴unsplash這個網站,現在是誰在發送這個request。 至於Authorization內容為何,同樣從文件中可得到答案: Most actions can be performed without requiring authentication from a specific user. For example, searching, fetching, or downloading a photo does not require a user to log in. To authenticate requests in this way, pass your application’s access key via the HTTP Authorization header: ```! Authorization: Client-ID YOUR_ACCESS_KEY ``` 其中YOUR_ACCESS_KEY即是從Keys產出的Access Key(可以從your apps查看) ![](https://hackmd.io/_uploads/SyEzne5in.png) 2. 第二個參數"params" params裡放置"query",而這個query為我們搜尋圖片的關鍵字,所以query會隨使用者搜尋的單詞不同而不一樣。 這裡為了示範,先用`cars`作為搜尋關鍵字,之後會再做修改。 ::: 完成`api.js`檔案的內容之後,我們將它import進`index.js`檔案中並執行該專案。可以看見我們印出了以下內容: ![](https://hackmd.io/_uploads/BkaVbZ9sn.png) 接著展開"data" > "result"看看內容: ![](https://hackmd.io/_uploads/rkZi-bcs2.png) 我們單看其中一個物件(這裡看'0'): ![](https://hackmd.io/_uploads/HkRAbb9in.png) 而這些內容,對我們而言,最重要的內容即"urls"的資料,也是這次專案會需要運用到的東西。 **** ## 5-65. [Optional] Using Async:Await 在[5-62](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?both#5-62-Overview-of-HTTP-Requests)中曾提到一個問題,發送請求之後,需要等待一段時間才會獲得回應,而因為JS為單執行緒的語言,所以他會同步執行,也就是說還沒獲得回應,JS就會立即執行下一行程式。因此在[5-62](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?both#5-62-Overview-of-HTTP-Requests)我們有稍微提到發送請求後,需要告訴JS等一等,直到我們獲得回應後,才能往下執行。 這樣的動作,便需要由async、await來達成。 ![](https://hackmd.io/_uploads/SyWCCK_oh.jpg) 正確的作法實作方式: ![](https://hackmd.io/_uploads/Bkbpsdjj2.jpg) **** ## 5-66. Data Fetching Cleanup 前面完成的半成品程式碼: **api.js** ```javascript! import axios from "axios"; const searchImages = async () => { const response = await axios.get('https://api.unsplash.com/search/photos', { headers: { Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4' }, params: { query: 'cars' } }) console.log(response); return response; } export default searchImages; ``` 1. 但可以注意到這邊的query永遠是cars,但我們希望可以讓使用者輸入任意的關鍵字 因此首先我們給予一個變數叫做"term"作為參數傳入`searchImages`的function中。 2. 可以看到目前`creturn`的內容為"response",不過這個response代表的是我們抓到的「所有」資料,但我們希望只抓到我們所需的資料即可 下圖為變數response物件,我們只想要抓它的results的部份,可以看見他接在response底下的data底下,因此利用物件取值方式,也就是`response.data.results`。 ![](https://hackmd.io/_uploads/rJ1Q-Yoj3.png) ```javascript! import axios from "axios"; const searchImages = async (term) => { const response = await axios.get('https://api.unsplash.com/search/photos', { headers: { Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4' }, params: { query: term } }) return response.data.result; } export default searchImages; ``` **** ## 5-67. Thinking About Data Flow 我們在這章節要思考,資料是如何在React App中流動的。以下我們可以先思考這個App可以做哪些事情?會做哪些事? 1. SearchBar這個元件中,包含一個文字輸入框,可以讓使用者輸入任意文字 2. 當使用者在文字輸入框按下"enter"後,代表我們要開始搜尋使用者輸入的關鍵字 3. 從[5-64](h[ttps://](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg#5-64-Making-an-HTTP-Request))的後方有提到我們可以抓到一個物件,而使用者搜尋的內容將被存放進該物件中的一個陣列中。 4. 而該存放著圖片的陣列,必須傳至我們的ImageList元件檔案中 大致知道整個取得資料、回傳資料的流程後,我們再來要思考:要在什麼地方去呼叫`searchImage()`這個函式呢?(複習一下,該函式在`api.js`中,所以我們要思考該把這個函式import到哪裡) **錯誤想法** 我們第一個想法可能會是這樣,想著我們要傳一個"term"變數進入`searchImage()`這個函式,而"term"變數會在`SearchBar.js`中,當使用者輸入並按下enter之後取得,再執行`searchImage()`這個函式,並把結果回傳給`SearchBar.js`的兄弟元件`ImageList.js`(如下圖): ![](https://hackmd.io/_uploads/HJxKctjih.png) :::success (兄弟元件為sibiling component,同一層級的元件) 不過,在React中,如果兩元件為兄弟元件,他們之間是不知道對方的存在的(聽起來怎麼有點可悲)。 所以說是沒有任何方法可以讓這兩個元件之間做連接,也就是兄弟元件之間無法傳遞任何的資料。 ::: **正確想法** 所以我們必須找到這對兄弟元件的父母,這樣才能把資料分享給他們兩個,讓他們稍微變得有關連(?) 而我們可以從圖中輕易找到這對兄弟元件的父母就是`App.js`啊!!!所以我們將`searchImages()` import到`App.js`檔案中,接著就可以將"term"變數從`SearchBar.js`取得給`App.js`,並且將`searchImages()` 所return的"response.data.results"傳給`ImageList.js`檔案。(如下圖) 而至於如何實作?這就要依靠先前學過得"props system"了(props可以透過parent傳至children),所以我們將利用props來把images從`App.js`傳到`ImageList.js`。 ![](https://hackmd.io/_uploads/H1jnCYson.png) **** ## 5-68. Child to Parent Communication 先複習一下props system: ![](https://hackmd.io/_uploads/rJwLgcoon.png) 老師提到「props只能從parents傳入給child component」這句話不太準確。 因為我們現在就要來實作,將變數"term"透過props system從parents傳入給child component! ![](https://hackmd.io/_uploads/H1SSNqis3.jpg) **** ## 5-69. Implementing Child to Parent Communication 從[5-67](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg#5-67-Thinking-About-Data-Flow)可以大概知道我們想要完成的目標。 ![](https://hackmd.io/_uploads/HJSRMyps3.png) 不過我們先放大`App.js`與他的子元件`SearchBar.js`之間的關係: ![](https://hackmd.io/_uploads/HkIZEJ6sh.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">App.js</h3> </div> 首先,要在`App.js`中import SearchBar Component,並顯示至畫面上。並且設置一個function,命名為`handleSubmit()`,且我們會傳入參數"term"給`handleSubmit()`該function。 另外,要在SearchBar Component上加上事件等待使用者觸發, 亦即"onSubmit = {handleSubmit} ",這裡事件名稱為"onSubmit",當"onSubmit"事件被觸發,則會執行函式handleSubmit。 **App.js** ```javascript! import SearchBar from './components/SearchBar' function App(){ const handleClick = (term) => { console.log("Do a search for "+term); } return(<div> <SearchBar onSubmit = {handleClick}/> </div>); } export default App; ``` BTW, onSubmit以及handleSubmit只是其中一種命名方法,是可以依照自己喜好去任意命名的。 <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">SearchBar.js</h3> </div> 接著,看到`SearchBar.js`的部份,回想一下前面剛在`App.js`中使用了SearchBar component,並且在該component中添加了屬性"onSubmit={handleClick}",新增屬性後,React會將我們添加的屬性放進一個props object中,再來就要把props作為「引數」傳入到`App.js`的子元件`SearchBar.js`中。 另外,這裡先將"car"值賦予給term變數,並從子元件(`App.js`)回傳給父元件(`SearchBar.js`)。 (這部份忘記可以參考[3-26](https://hackmd.io/yIqdj6sHS7OGPSN38Z_Miw#3-26-Introducing-the-Props-System)) **SearchBar.js** ```javascript! function SearchBar({onSubmit}){ const handleClick = () => { onSubmit("car"); } return(<div> <input /> <button onClick={handleClick}>Click</button> </div>) } export default SearchBar; ``` 這章節的實作方法有點打破以往的印象:「只能將資料從父元件傳給子元件」,這裡把term="car"從子元件帶回給父元件了!以下是這節的統整圖: ![](https://hackmd.io/_uploads/ByO4Keaj3.jpg) **** ## 5-69. Implementing Child to Parent Communication 老師在上一章節運用button,使整個流程看起來更清楚,以及不管使用者在input輸入什麼內容,都只有回傳固定的"car",因此我們將在這個章節中做修改。 先回到最原始的狀態: **SearchBar.js** ![](https://hackmd.io/_uploads/BJjEfW6s2.png) 那首先針對HTML做修改,我們應該把`<input />` tag放在`<form></form>`中間,這樣當使用者在`<input />` 輸入內容,一些存在於瀏覽器自動的行為才會起作用(kick in)。 也就是你在`<input />` 輸入內容並按下enter鍵,你的瀏覽器就會自動觸發(trigger)`<form></form>`身上的提交事件(submit event)。 **** ## 5-70. Handling Form Submission 這裡會再次談到關於form以及input的一些預設方法,但這些預設可能會對於這次專案有些問題,所以我們需要先從HTML去了解這兩者。 直接來看W3C的spec:[Forms](https://www.w3.org/TR/html401/interact/forms.html) (PS input在form目錄之下) 其中應該跟[以下內容](https://www.w3.org/TR/html401/interact/forms.html#current-value)較相關(先不深入) > The control's "current value" is first set to the initial value. Thereafter, the control's current value may be modified through user interaction and scripts. > > A control's initial value does not change. Thus, when a form is reset, each control's current value is reset to its initial value. If a control does not have an initial value, the effect of a form reset on that control is undefined. 在預設下,我們在`<input />` 輸入內容並按下enter鍵,你的瀏覽器就會自動觸發(trigger)`<form></form>`身上的提交事件(submit event),之後原先在`<input />` 輸入的內容,就會立即被清空(可以想像就是被提交出去的感覺) 不過我們希望是作一個搜尋引擎,想像一下假如我們在google搜尋資料,每次打上關鍵自按下enter後,搜尋框的內容被清掉是不是會很讓人感到困擾? 因此這裡我們可以利用一個語法來阻止預設的情形發生。 ```javascript event.preventDefault(); ``` 那麼就可以來實作了! **App.js** ```javascript! import SearchBar from './components/SearchBar' function App(){ const handleClick = (term) => { console.log("Do a search for "+term); } return(<div> <SearchBar onSubmit = {handleClick}/> </div>); } export default App; ``` **SearchBar.js** ```javascript! function SearchBar({onSubmit}){ const handleFormSubmit = (event) => { event.preventDefault(); onSubmit("car"); } return( <form onSubmit={handleFormSubmit}> <input /> </form>) } export default SearchBar; ``` 不過會發現目前還是沒有抓取使用者在input欄位輸入的內容,下章節將會進行說明。 **** ## 5-71. Handling Input Elements 接著這章節將會說明如何抓取input裡,使用者所輸入的資料。 一開始我們可能會想,不就是先抓取input 這個DOM元素,然後再取他的value,這樣不就可以抓到input裡面的內容了嗎?(想法如下) **SearchBar** ```javascript! function SearchBar({onSubmit}){ const handleFormSubmit = (event) => { event.preventDefault(); onSubmit(document.querySelector('input').value); } return( <form onSubmit={handleFormSubmit}> <input /> </form>) } export default SearchBar; ``` 當我們嘗試執行這段程式碼,會發現他是可以執行的,沒什麼問題,但是!!!!! :::danger 老師提醒:絕對不要寫出這種方法!!!!!! ![](https://hackmd.io/_uploads/ryy4oUknh.png) 老師提到,在react中不要用類似query selector的方式嘗試抓取input的資料,老師還說寫出這種方法,在面試中保證會被拒絕掉。 那這是因為React在處理form的一些元件(例如說text input、checkboxes、radio button等等)有一點點的特別,他大致上有5個步驟,接下來會繼續說明。 ::: ![](https://hackmd.io/_uploads/B1-F6UJ23.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">Step1. Create a new piece of state</h3> </div> 先創建新的State,照前面所學運用useState來創建。 ![](https://hackmd.io/_uploads/ryWWUD1nh.jpg) **SearchBar.js** ```javascript= import {useState} from 'react'; function SearchBar({onSubmit}){ const [term, setTerm] = useState(''); const handleFormSubmit = (event) => { event.preventDefault(); onSubmit("car"); } return( <form onSubmit={handleFormSubmit}> <input /> </form>) } export default SearchBar; ``` <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">Step2. Create an event handler to watch for the 'onChange' event.</h3> </div> 接著,要利用'onChange'來觀察使用者在input中做了哪些事情?(例如說複製、貼上、輸入、刪除等等) 所以我們新增onChange事件到input上,當onChange被觸發,那麼就會執行handleChange。 ![](https://hackmd.io/_uploads/rJePnwy2n.jpg) ```javascript! import {useState} from 'react'; function SearchBar({onSubmit}){ const [term, setTerm] = useState(''); const handleFormSubmit = (event) => { event.preventDefault(); onSubmit("car"); } const handleChange = () =>{ } return( <form onSubmit={handleFormSubmit}> <input onChange={handleChange}/> </form>) } export default SearchBar; ``` <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">Step3. When the 'onChange' event fires, get the value from the input.</h3> </div> 首先,我們只要在input裡面有任何的動作,就等於觸發onChange這個event,所以我們先試著在觸發時,讓瀏覽器印出"event"物件(如下圖) **觸發onChange時會執行的函式(印出"event")** ![](https://hackmd.io/_uploads/BkZze_J2h.png) **event物件們** ![](https://hackmd.io/_uploads/r1WMgdkhn.png) 而其中我們最關心event物件中的value,我們只要稍微找一下,就可以發現value出現在event物件底下的taget之中:(下圖中最後一行) ![](https://hackmd.io/_uploads/SyLsx_kh2.png) 所以這裡我們先改為印出event.target.value看看。 ![](https://hackmd.io/_uploads/BkreW_J2h.png) 此時就可以看見我在input中逐字輸入字串的內容 ![](https://hackmd.io/_uploads/BkuEbukh2.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">Step4. Take that value from the input and use it to update your state.</h3> </div> 再來我們除了要拿到input的內容之外,還要去更新state。而其中去改變、更新state的,就是setState,亦即此處的setTerm。作法很簡單,先把console.log()改成setTerm()即可。 意思就是每當onChange event被觸發,就會執行handleChange,而該函式會做的事情就是setTerm(event.target.value),用文字說明即每次更新就會把"event.target.value"重新渲染給term。 ![](https://hackmd.io/_uploads/Skxnf_1nn.png) <div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px"> <h3 style="margin: 0">Step5. Pass your state to the input as the value prop.</h3> </div> 接著,把term(term即每次使用者在input有任何動作,就會被重新渲染成使用者在input的輸入內容)作為input中的value prop。 ![](https://hackmd.io/_uploads/SyMAVdJn2.jpg) **** ## 5-72. [Optional] OK But Why? 延續上一章節,這章節將解釋上一張節的步驟為什麼要這麼做呢? 首先,第一個要談的是`<input />` 裡的value prop,先以HTML角度來看,`<input />` 擁有value屬性,以我們下方程式碼的例子來看,沒有設置type屬性,因此會視為預設type="text"。 ```htmlembedded! <input value="hi there!" /> ``` ![](https://hackmd.io/_uploads/SkKPsal3n.png) 而我們可以從W3C中(下圖)得知,type="text"時,value屬性的值,即為初始狀態時input的輸入欄位中會顯示的內容。 ![](https://hackmd.io/_uploads/Sk7vaax3n.png) 因此,當我們在上一章節,寫出如下的內容,代表的就是使用者在input欄位輸入什麼,都會顯示在欄位內 ```htmlembedded! <input value={term} /> ``` 老師提供上章節整個運作的流程圖: ![](https://hackmd.io/_uploads/S1iexAg2h.png) 所以為什麼在React中,抓取input裡面的值要怎麼麻煩呢???? ![](https://hackmd.io/_uploads/HJZag0x23.png) **** ## 5-75. Reminder on Async:Await 看到App.js的部份,可以發現若我們只是一般的使用api.js中的`searchImage()`,那他印出的結果會是下方第二張圖片。 **同步:App.js** ![](https://hackmd.io/_uploads/rkw7LF732.png) **同步:印出結果** 會得到一個promise物件,裡面寫著pending,代表執行`searchImage()`後他還在處理,他需要一點時間,但前面提過JS是同步語言,所以JS不給任何時間,會立即執行下一行。 ![](https://hackmd.io/_uploads/H1d4IKmhn.png) 因此,我們這裡要加上async、await讓這段程式碼變成非同步處理。 **非同步:App.js** ![](https://hackmd.io/_uploads/BJJJoKmh2.png) **非同步:印出結果** ![](https://hackmd.io/_uploads/S1T1iY7hh.png) **** ## 5-76. Communicating the List of Images Down 在上一個章節中,我們可以很清楚「result變數」代表著使用者所輸入的內容,搜尋到的圖片。 這裡我們想將「result變數」,顯示在「ImageList」這個component上。 也就是說,我們希望每次輸入者輸入內容後,「ImageList」這個component上的圖片可以不斷的更新(update)。而在React中提到update,百分之百就是要使用到==state system==!!!! **App.js** ```javascript import { useState } from 'react'; import SearchBar from './components/SearchBar'; import ImageList from './components/ImageList'; import searchImages from './api'; function App(){ const [images, setImages] = useState([]); const handleClick = async (term) => { const result = await searchImages(term); setImages(result); } return(<div> <SearchBar onSubmit = {handleClick}/> <ImageList images={images}/> </div>); } export default App; ``` **ImageList.js** ```javascript function ImageList({images}){ return<div>ImageList:{images.length}</div> } export default ImageList; ``` 很酷的是,可以發現`images`在App.js中視作為state使用,而在ImageList中則作為props使用,這意味著一個變數可以同時作為state以及props。 **作為state使用** ![](https://hackmd.io/_uploads/HkzaHdNn3.png) **作為props使用** ![](https://hackmd.io/_uploads/H1fTHON3h.png) **** ## 5-77. Building a List of Images 我們可以從下圖看到,我們從API取得的圖片,有相當多的資訊,不過我們主要來看"id",因為id是唯一的,可以用來代表一張圖片。除此之外,我們還要利用前面章節學過得`map()`幫我們將圖片轉換成component的一部分。 ![](https://hackmd.io/_uploads/Sk8RPdE22.png) 底下的流程圖,便是我們想要達成的: ![](https://hackmd.io/_uploads/SyVkFdVh3.png) **ImageList** 利用map() ```javascript! import ImageShow from './ImageShow'; function ImageList({images}){ const renderedImages = images.map((image) => { return <ImageShow image = {image}/> }) return<div>{renderedImages}</div> } export default ImageList; ``` **ImageShow** ```javascript! function ImageShow({image}){ return<div>{image.id}</div> } export default ImageShow; ``` **呈現畫面** 接著就可以看到螢幕上,顯示使用者輸入內容,透過API抓到的十筆資料的id了。 ![](https://hackmd.io/_uploads/SkeC9d423.png) 不過來到這裡會遇到一個error: ![](https://hackmd.io/_uploads/rkdzjuN22.png) 他要我們加上一個"key" prop!而這將會在下一節提到。 **** ## 5-78. Handling List Updates 在開始之前,我們先把id給替換掉,換成alt_description,這樣對我們來說辨別度比較大,比較好操作。 ![](https://hackmd.io/_uploads/rkRy9-v2h.png) 改完之後,應該可以看到頁面顯示改為下圖: ![](https://hackmd.io/_uploads/ryXX9bvhn.png) 以下是我們想做的事情,將圖片物件中的alt_description提出,放到ImageShow元件中,並且這麼描述最後要放入div裡面。 ![](https://hackmd.io/_uploads/HJFyjWDh2.png) 接著,要記得一件事情,每當React中的state更新了,他就會rerender要變動的元件,以及底下的子元件。 那麼如何更新呢?老師給了plan A跟plan B **Plan A** 移除所有存在的HTML,再整個全部重新建立 這種方式會成功,但是太耗能了,所以我們不應該使用這個辦法 ![](https://hackmd.io/_uploads/rJXoMGDn3.png) **Plan B** 那麼就是要利用Plan B了!在step 0時,我們先加上key props,並且賦予這個key的值為image.id。 再來,進入第一步驟,重新render後,react會去比較先前與目前的key,知道有哪些變化。 ![](https://hackmd.io/_uploads/B1zT4Mvh2.png) 第二步驟,react會以最少的步驟,去改變已存在的DOM元素。 ![](https://hackmd.io/_uploads/rk82Lfvhh.png) **** ## 5-79. Notes on Keys ![](https://hackmd.io/_uploads/SJ84uMDh2.png) 首先,對ImageShow元件加上key props,此時再回到瀏覽器會發現不會在顯示error了(5-77曾提過得error)。 ![](https://hackmd.io/_uploads/BJD0vMDn3.png) 第二點中,提到'Add the key to the top-most JSX element in the list',表示著記得把key移至整個JSX中的最外層(例如最外層是div,那請把key放在div這一層之上)。 另外可能會有些疑問,我們在第四節的動物專案中並沒有id,那當時的key的值是什麼呢?答案是'index',這就是當初都沒說的index的用處,index就是賦予給key props 的值。 ![](https://hackmd.io/_uploads/ByuoTMDhh.png) **** ## 5-80. Displaying Images 再來,要把圖片顯示在螢幕之上了。我們可以從API取得的資訊中看到"urls",而底下有幾種不同格式,而為了減輕整個app的花費,我們這邊會選擇"small"的連結。 ![](https://hackmd.io/_uploads/ryWFZNP23.png) ![](https://hackmd.io/_uploads/rJLqZNwhn.png)