# 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