# 1.25 React ###### tags: `React` ## 零、目錄&待辦事項 [TOC] ## 壹、元件的狀態&屬性複習 ## 貳、元件溝通方式 ### 一、父母向子女 -> 塞在props給子女 開檢查工具看Component: App > Parent > Child ```javascript= // App.js // App.js 接下來都相同 import Parent from './components/Parent' function App(){ return <> <Parent /> </> } export default App // Parent.js import Child from './Child' function Parent(){ const innerData = '父母元件內部資料' return <Child data={innerData}/> } export default Parent //Child.js function Child(props){ // 利用props得到由父母元件傳遞過來的資料 return <h1>{props.data}</h1> } export default Child ``` ### 二、子女向父母 -> 父母給子女function,子女塞在function的參數,送回給父母 React是單向資料流,沒辦法直接子女傳向父母,要用間接的方式才可。 App > Parent > Child ```javascript= // Parent.js import Child from './Child' import {useState} from 'react' function Parent(){ const [data, setData] = useState('') return ( <> <h1>{data}</h1> <Child setData = {setData} /> // 把父母元件的函式傳給子女,式子女透過這個函式設定data // 前面的setData是Child的屬性(?) // 後面的{setData}是解構來的函式,會做設定狀態的動作,他會作為props傳給子女,子女把參數就進去,傳回給父母得到data值 </> ) } export default Parent // Child.js function Child(props){ const innerData = '子女元件內部資料' return ( <button onClick={()=>{ // 利用props得到的setData函式來設定資料給父母元件 props.setData(innerData) }}> 送資料給父母元件 </button> ) } export default Child ``` ### 三、子女對子女,一、二的綜合型,子女一先塞在funciton給父母,父母得到後塞在props給子女二 App > Parent > ChildOne、ChildTwo 要透過父母才能傳遞資料 One送給Parent,Parent再送給Two ```javascript= // Parent.js import ChildOne from './ChildOne' import ChildTwo from './ChildTwo' import {useState} from 'react' function Parent(){ const [data, setData] = useState('') return ( <> // One設定資料給Parent、Parent傳給Two <ChildOne setData = {setData} /> <ChildTwo data = {data} /> </> ) } export default Parent // ChildOne.js function ChildOne(props){ const innerData = 'ChildOne子女元件內部資料' return ( <button onClick={()=>{ props.setData(innerData) }}> 送資料給ChildTwo元件 </button> ) } export default ChildOne // ChildTwo.js function ChildTwo(props){ const innerData = '子女元件內部資料' return ( <h1>{props.data}</h1> ) } export default ChildTwo ``` ### 四、任何元件互傳 * 用第三方函式庫或用鉤子(react context api) * react context api是個用來傳遞資料的鉤子,但要搭配useContext、useReducer使用,初學者太難。 ### 五、實例-4.以props作為狀態的初始值 (AntiPattern) * 利用props來設定狀態的初始,容易造成執行上錯誤 * 解決方法:不要讓子女元件有狀態,其狀態是來自於父母 * 這裡的解法也不是好解法,之後要用生命週期方法來解決 * App > CountParent > CountFunc ```javascript= // App.js import CountParent from './components/CountParent' function App() { return ( <> <CountParent /> </> ) } export default App ``` ```javascript= // components/CountParent.js import { useState } from 'react' import CountFunc from './CountFunc' function CountParent() { // 使用狀態 const [initNumber, setInitNumber] = useState(0) return ( <> <CountFunc initNumber={initNumber} /> <button onClick={() => { setInitNumber(10) }} > 設定一開始為10 </button> <button onClick={() => { setInitNumber(100) }} > 設定一開始為100 </button> </> ) } export default CountParent ``` ```javascript= // components/CountFunc.js import React, { useState } from 'react' function CountFunc(props) { // const [total, setTotal] = useState(props.initNumber) // 利用props做為狀態的初始值,這種反模式antipattern會造成錯誤 const [total, setTotal] = useState(0) return ( <> // 這行會是錯的 // <h1>{total}</h1> <h1>{props.initNumber + total}</h1> <button onClick={() => { setTotal(total + 1) }} > +1 </button> <button onClick={() => { setTotal(total - 1) }} > -1 </button> </> ) } export default CountFunc ``` ### 六、實例-5.以props作為狀態的初始值-利用父母元件的state * 子女元件一開始不要有狀態,由父母元件的狀態傳給子女作為初始值 ```javascript= // components/CountParent.js import { useState } from 'react' import CountFunc from './CountFunc' function CountParent() { // 使用狀態 const [total, setTotal] = useState(0) return ( <> <CountFunc initNumber={initNumber} total={total} setTotal={setTotal}/> <button onClick={() => { setTotal(10) }} > 重設定一開始為10 </button> <button onClick={() => { setTotal(100) }} > 重設定一開始為100 </button> </> ) } export default CountParent ``` ```javascript= // components/CountFunc.js import React, { useState } from 'react' function CountFunc(props) { // 不要讓子女元件有狀態,子女元件的狀態要來自於父母 // const [total, setTotal] = useState(0) const {total, setTotal} = props return ( <> <h1>{total}</h1> <button onClick={() => { setTotal(total + 1) }} > +1 </button> <button onClick={() => { setTotal(total - 1) }} > -1 </button> </> ) } export default CountFunc ``` ## 參、生命週期 [講義在這裡](https://github.com/eyesofkids/mfee11-react/blob/main/%E6%95%99%E6%9D%90/0121/react%E6%8A%95%E5%BD%B1%E7%89%87-%E7%8B%80%E6%85%8B-%E5%B1%AC%E6%80%A7-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.pdf) [有個圖形解說的在這裡](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) ### 一、生命週期方法:掛載、更新、卸載 * 生命週期方法只有類別型元件有,函式型元件式有類似概念的方法 #### 1. 掛載 mounting * 從無到有,程式已經運算好了,畫面尚未呈現。 * 從無到有的過程: * 虛擬dom程式碼 * 經由ReactDom render產生 * constructor 類別型元件建構(雖然沒有建構式) * 元件初始化 * ~~getDrivedStateFromProps 從屬性得到延伸的狀態~~(用來做程式的最佳化,跳過) * render 類別的render、就像函式型元件的回傳值 * 元件呈現到網頁上 * componentDidMount 元件已經掛載,這個是最重要的 * react的虛擬dom已經真正呈現在網頁上,和一般的dom無異,可以和其他JS、JQ等程式整合了 * constructor、render、componentDidMount是重點 * constructor > render > componentDidMount #### 2. 更新 Updating 已經存在後,更新內容,重新渲染,網頁重新呈現 1. 更新內容 * react元件更新只有3種方式 1. setState 按了這個按鈕後用setState去做重新設定 2. New Props 從父母元件收到新的設定值 3. ~~forceUpdate 這也是最佳化的部分~~,跳過 2. render產生頁面 3. componentDidupdate,第二重要的生命週期方法 * ~~shouldComponentUpdate、getSnapshotBeforeUpdate,最佳化~~,跳過 * render > conmponentDidUpdate #### 3. 卸載 Unmounting * 從有到無 * componentWillUnmount,和掛載相對,掛載有什麼,就要卸載什麼。 * 像是有監聽就要卸載監聽、有計時器就要卸載計時器 * componentWillunmount ## 肆、實例-6.類別型元件-生命周期方法-掛載 ### 生命週期方法本來就是設計給類別型元件用的 函式型沒有constructor,沒有render三方法,沒有componentDidMount,沒有componentWillUnMount,但他有自己的生命週期方法 ```javascript= // App.js // CountClass.js import React from 'react' class CountClass extends React.Component{ constructor(){ ... } componentDidMount(){ ... 在這裡面才可以掛事件監聽、JS、JQ程式碼 } componentDidUpdate(){ ... setState、New props二種方法更新 } componentWillUnmount(){ ... 元件消失在網頁上 與componentDidMount相對 componentDidMount加掛了什麼 componentWillUnmount就是卸載調它 要在App.js裡面多一個狀態,使元件消失時就會呼叫componentWillUnmount } render(){ ... } } // App.js // 卸載的話要多一個切換 import { useState } from 'react' import CountClass from './components/CountClass' function App() { const [show, setShow] = useState(true) return ( <> {show && <CountClass />} <hr /> <button onClick={() => { setShow(!show) }} > {show ? '消失吧' : '復活吧'} </button> </> ) } export default App ``` * react內沒有if/else的語法,要用 ```javascript= 1 ? 2 : 3 if (1){ 執行2 } else 3 1 && 2 if (1){ 執行2 } ``` ## 伍、ES6補充 Side Effects(副作用)與Pure Functions(純粹函式) [補充講義在這裡](https://github.com/eyesofkids/mfee11-react/issues/9) * 副作用:主要作用外,額外產生的其他作用。 * 除了函式的執行外,可能會改變到其他或外部狀態、呼叫到其他函式。 * 表達式或函式可能會造成副作用 * 例如JavaScript的自動轉型,最好使用嚴格相等比較(=\==)與(!=\=),而不要使用值相等比較==與!=,是因為要避免任何不經意的"副作用" * 純粹函式:不會造成副作用的函式 * 給定相同的輸入,會有相同的輸出 * 不依賴外部狀態(外部的變數) * 如果程式碼內有非同步、資料的輸入輸出,就是會有副作用 ## 陸、Hooks [講義在這裡](https://github.com/eyesofkids/mfee11-react/blob/main/%E6%95%99%E6%9D%90/0121/React%E5%8B%BE%E5%AD%90(hooks)/intro.pdf) * 函式型元件可以用鉤子,類別型元件不能 * 類別型元件可以用生命週期方法,函式型元件不能 * 函式型元件會用Hooks來模擬出生命週期方法,也就是useEffect * useEffect指的是使用副作用方法 ```javascript= // CountFunc.js import React, {useState, useEffect} from 'react' function Count(){ // 沒有明確地模擬建構,只有設定狀態初始值 const [total, setTotal] = useState(0) // 模擬出componentDidMount // 2個參數,第1個是CB,第2個是陣列。陣列是用來處理update的,這裡一開始可以是空的。 useEffect(()=>{模擬mount},[]) // 模擬出componentDidUpdate,但也有componentDidMount的執行過程 // 要寫if條件式, useEffect(()=>{if(){ ... } },[要更新的狀態或傳入props]) // 模擬componentWillUnmount useEffect(()=>{ retrun()=>{componentWillUnmount} },[]) } useEffect(()=>{},[]) useEffect(useEffect的CB,[相依性的陣列]) 相依性的陣列是要放要更新的狀態,有變化時才執行 ``` ## 柒、useEffect的常見用法 [在issue的這裡](https://github.com/eyesofkids/mfee11-react/issues/7) ## 捌、實例11、12-美金台幣戶轉 * react的表單元素較特別,分為可控制、不可控制 * 可控制:React Component元件管理,推薦使用。要記得值對應到狀態、觸發事件對應到設定狀態的方法。 * 不可控制:由實際的DOM管理 * 我們用可控的! * 可控的表單元素有二個條件 1. input的值對應到狀態 2. 變動(更新)的事件對應到設定狀態的方法 * 在檢查工具中,hooks的State沒有名字,會照順序排下來而已,要自己注意 * 上傳檔案是不可控的 ## 玖、範例13-多個輸入欄位(FormFields.js) * ios的safari對於input type = date、time都不支持 * 多欄位->類別型元件,在官網的form說明文件找到多個輸入 * 函式型:要用狀態內的物件多個屬性去達成 ## 拾、範例15-狀態合併機制、與函式型元件的差異 ## 拾、實例:台灣郵遞區號連動式 ```javascript= // src/data/township.js 記得把這個加到data目錄 // components/ZipCode.js import {useState} from 'react' import {countries, townships, postcodes} from './data/townships' function ZipCode(){ console.log(countries, townships, postcodes) const [country, setCountry] = useState(-1) const [country, setCountry] = useState(-1) return ( <> <select value={country} onChange={(e)=>{ setCountry(+e.target.value) setTownship(-1) }}> <option value="-1">請選擇縣市</option> {countries.map((value,index)=>{ <option key = {index} value={index}>{value}))} </option> </select> <select> <option>請選擇區域</option> </select> <h3>郵遞區號</h3> </> ) } export default ZipCode // App.js import ZipCode from './components/ZipCode' function App(){ return( <> <ZipCode/> </> ) } export default App ``` 有一個按鈕,他是藍色的,點擊它後會變成紅色,再點擊則變回藍色 按鈕>元件 顏色>狀態 按鈕產生,被放到畫面上,按鈕的顏色改變了 mounting: 準備好元件 componentdidmount: 程式已經把運算完成,虛擬DOM完成,但還沒放到畫面上 updating: 更新狀態 unmounting: 把元件以及狀態消除掉 componentWillMount componentDidMount shouldComponentUpdate: React是透過人工的方式去判斷元件要不要改變,通常是比對現在跟下一個狀態是否不同,若不同就更新 componentWillUpdate