明星用什么軟件做視頻網(wǎng)站友情鏈接方面
useReducer
usereducer
相當(dāng)于 復(fù)雜的 useState
當(dāng)狀態(tài)更新邏輯較復(fù)雜時(shí)可以考慮使用 useReducer。useReducer 可以同時(shí)更新多個(gè)狀態(tài),而且能把對(duì)狀態(tài)的修改從組件中獨(dú)立出來。
相比于 useState,useReducer 可以更好的描述“如何更新狀態(tài)”。例如:組件負(fù)責(zé)發(fā)出行為,useReducer 負(fù)責(zé)更新狀態(tài)。
好處是:讓代碼邏輯更清晰,代碼行為更易預(yù)測(cè)。
1. useReducer 的語(yǔ)法格式
useReducer 的基礎(chǔ)語(yǔ)法如下:
const [state, dispatch] = useReducer(reducer, initState, initAction?)
其中:
1. reducer 是一個(gè)函數(shù),類似于 (prevState, action) => newState。形參 prevState 表示舊狀態(tài),形參 action 表示本次的行為,返回值 newState 表示處理完畢后的新狀態(tài)。
2. initState 表示初始狀態(tài),也就是默認(rèn)值。
3. initAction 是進(jìn)行狀態(tài)初始化時(shí)候的處理函數(shù),它是可選的,如果提供了 initAction 函數(shù),則會(huì)把 initState 傳遞給 initAction 函數(shù)進(jìn)行處理,initAction 的返回值會(huì)被當(dāng)做初始狀態(tài)。
4. 返回值 state 是狀態(tài)值。dispatch 是更新 state 的方法,讓他接收 action 作為參數(shù),useReducer 只需要調(diào)用 dispatch(action) 方法傳入的 action 即可更新 state。
2. 定義組件的基礎(chǔ)結(jié)構(gòu)
定義名為 Father 的父組件如下:
import React from 'react'// 父組件
export const Father: React.FC = () => {return (<div><button>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}
定義名為 Son1 和 Son2 的兩個(gè)子組件如下:
// 子組件1
const Son1: React.FC = () => {return <div className="son1"></div>
}// 子組件2
const Son2: React.FC = () => {return <div className="son2"></div>
}
在 index.css 中添加對(duì)應(yīng)的樣式:
.father {display: flex;justify-content: space-between;width: 100vw;
}.son1 {background-color: orange;min-height: 300px;flex: 1;padding: 10px;
}.son2 {background-color: lightblue;min-height: 300px;flex: 1;padding: 10px;
}
3. 定義 useReducer 的基礎(chǔ)結(jié)構(gòu)
1、按需導(dǎo)入 useReducer 函數(shù):
import React, { useReducer } from 'react'
2、定義初始數(shù)據(jù):
const defaultState = { name: 'liulongbin', age: 16 }
3、定義 reducer
函數(shù),它的作用是:根據(jù)舊狀態(tài),進(jìn)行一系列處理,最終返回新狀態(tài):
// 第一個(gè)參數(shù)永遠(yuǎn)是上一次的舊狀態(tài)
const reducer = (prevState) => {// 首次進(jìn)入頁(yè)面 不會(huì)觸發(fā) reducer 函數(shù)執(zhí)行console.log('觸發(fā)了 reducer 函數(shù)')// 必須向外返回一個(gè)處理好的新狀態(tài) return prevState
}
4、在 Father
組件中,調(diào)用 useReducer(reducerFn, 初始狀態(tài))
函數(shù),并得到 reducer
返回的狀態(tài):
// 父組件
export const Father: React.FC = () => {// useReducer(fn, 初始數(shù)據(jù), 對(duì)初始數(shù)據(jù)進(jìn)行處理的fn)const [state] = useReducer(reducer, defaultState)console.log(state)return (<div><button>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}
5、為 reducer
中的 initState
指定數(shù)據(jù)類型:
// 定義狀態(tài)的數(shù)據(jù)類型
type UserType = typeof defaultStateconst defaultState = { name: 'liulongbin', age: 16 }// 給 initState 指定類型為 UserType
const reducer = (prevState: UserType) => {console.log('觸發(fā)了 reducer 函數(shù)')return prevState
}
6、接下來,在 Father 組件中使用 state 時(shí),就可以出現(xiàn)類型的智能提示啦:
// 父組件
export const Father: React.FC = () => {const [state] = useReducer(reducer, defaultState)console.log(state.name, state.age)return (<div><button>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}
4. 使用 initAction 處理初始數(shù)據(jù)
定義名為 initAction
的處理函數(shù),如果初始數(shù)據(jù)中的 age 為小數(shù)、負(fù)數(shù)、或 0 時(shí),對(duì) age 進(jìn)行非法值的處理:
const initAction = (initState: UserType) => {// 把 return 的對(duì)象,作為 useReducer 的初始值return { ...initState, age: Math.round(Math.abs(initState.age)) || 18 }
}
在 Father
組件中,使用步驟1聲明的 initAction
函數(shù)如下:
// 父組件
export const Father: React.FC = () => {// useReducer(fn, 初始數(shù)據(jù), 對(duì)初始數(shù)據(jù)進(jìn)行處理的fn)const [state] = useReducer(reducer, defaultState, initAction)// 省略其它代碼...
}
在定義 defaultState 時(shí),為 age 提供非法值,可以看到非法值在 initAction 中被處理掉了。
5. 在 Father 組件中點(diǎn)擊按鈕修改 name 的值
1. 錯(cuò)誤示范:
不要像 vue 響應(yīng)式數(shù)據(jù)一樣,直接通過 state.name = 'escook'
去修改
// 父組件
export const Father: React.FC = () => {// useReducer(fn, 初始數(shù)據(jù), 對(duì)初始數(shù)據(jù)進(jìn)行處理的fn)const [state] = useReducer(reducer, defaultState, initAction)console.log(state)const onChangeName = () => {// 注意:這種用法是錯(cuò)誤的,因?yàn)椴荒堋局苯有薷?state 的值】// 因?yàn)榇鎯?chǔ)在 useReducer 中的數(shù)據(jù)都是“不可變”的!// 要想修改 useReducer 中的數(shù)據(jù),必須觸發(fā) 【reducer】 函數(shù)的重新計(jì)算,// 根據(jù) reducer 形參中的舊狀態(tài)對(duì)象(initState),經(jīng)過一系列處理,返回一個(gè)“全新的”狀態(tài)對(duì)象state.name = 'escook'}return (<div><button onClick={onChangeName}>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}
2. 正確的操作
為了能夠觸發(fā) reducer 函數(shù)的重新執(zhí)行,我們需要在調(diào)用 useReducer() 后接收返回的 dispatch 函數(shù)。示例代碼如下:
// Father 父組件
const [state, dispatch] = useReducer(reducer, defaultState, initAction)
在 button 按鈕的點(diǎn)擊事件處理函數(shù)中,調(diào)用 dispatch() 函數(shù),從而觸發(fā) reducer 函數(shù)的重新計(jì)算:
// Father 父組件
const onChangeName = () => { dispatch()
}
點(diǎn)擊 Father 組件中如下的 button 按鈕:
<button onClick={onChangeName}>修改 name 的值</button>
會(huì)觸發(fā) reducer 函數(shù)的重新執(zhí)行,并打印 reducer 中的 console.log(),代碼如下:
const reducer = (prevState: UserType) => {console.log('觸發(fā)了 reducer 函數(shù)')return prevState
}
3. 調(diào)用 dispatch 傳遞參數(shù)給 reducer
在 Father 父組件按鈕的點(diǎn)擊事件處理函數(shù) onChangeName 中,調(diào)用 dispatch() 函數(shù)并把參數(shù)傳遞給 reducer 的第2個(gè)形參,代碼如下:
const onChangeName = () => {// 注意:參數(shù)的格式為 { type, payload? }// 其中:// type 的值是一個(gè)唯一的標(biāo)識(shí)符,用來【指定本次操作的類型】,一般為大寫的字符串// payload 是本次操作需要用到的數(shù)據(jù),為可選參數(shù)。在這里,payload 指的是把用戶名改為字符串 '劉龍彬'dispatch({type: 'UPDATE_NAME', payload: '劉龍彬'})
}
修改 reducer 函數(shù)的形參,添加名為 action
的第2個(gè)形參,用來接收 dispatch 傳遞過來的數(shù)據(jù):
const reducer = (prevState: UserType, action) => {// 打印 action 的值,終端顯示的值為:// {type: 'UPDATE_NAME', payload: '劉龍彬'}console.log('觸發(fā)了 reducer 函數(shù)', action)return prevState
}
在 reducer 中,根據(jù)接收到的 action.type
標(biāo)識(shí)符,決定進(jìn)行怎樣的更新操作,最終 return 一個(gè)計(jì)算好的新狀態(tài)。示例代碼如下:
const reducer = (prevState: UserType, action) => {console.log('觸發(fā)了 reducer 函數(shù)', action)// return prevStateswitch (action.type) {// 如果標(biāo)識(shí)符是字符串 'UPDATE_NAME',則把用戶名更新成 action.payload 的值// 最后,一定要返回一個(gè)新狀態(tài),因?yàn)?useReducer 中每一次的狀態(tài)都是“不可變的”case 'UPDATE_NAME':return { ...prevState, name: action.payload } // 解除引用,賦予新對(duì)象// 兜底操作:// 如果沒有匹配到任何操作,則默認(rèn)返回上一次的舊狀態(tài)default:return prevState}
}
4、為 action 指定類型
在上述的 switch...case...
代碼期間,沒有 TS
的類型提示,這在大型項(xiàng)目中是致命的。因此,我們需要為 reducer
函數(shù)的第2個(gè)形參 action
指定操作的類型:
// 1. 定義 action 的類型
type ActionType = { type: 'UPDATE_NAME'; payload: string }// 2. 為 action 指定類型為 ActionType
const reducer = (prevState: UserType, action: ActionType) => {console.log('觸發(fā)了 reducer 函數(shù)', action)// 3. 刪掉之前的代碼,再重復(fù)編寫這段邏輯的時(shí)候,會(huì)出現(xiàn) TS 的類型提示,非常 Niceswitch (action.type) {case 'UPDATE_NAME':return { ...prevState, name: action.payload }default:return prevState}
}
同時(shí),在 Father
組件的 onChangeName
處理函數(shù)內(nèi),調(diào)用 dispatch()
時(shí)也有了類型提示:
const onChangeName = () => {dispatch({ type: 'UPDATE_NAME', payload: '劉龍彬' })
}
注意:在今后的開發(fā)中,正確的順序是
? 1、先定義
ActionType
的類型? 2、修改
reducer
中的switch...case...
邏輯? 3、在組件中調(diào)用
dispatch()
函數(shù)!這樣能夠充分利用 TS 的類型提示。
6. 把用戶信息渲染到子組件中
1、在 Father 父組件中,通過jsx展開語(yǔ)法把 state
數(shù)據(jù)對(duì)象綁定為 Son1
和 Son2
的 props
屬性:
// 父組件
export const Father: React.FC = () => {const [state, dispatch] = useReducer(reducer, defaultState, initAction)const onChangeName = () => {dispatch({ type: 'UPDATE_NAME', payload: '劉龍彬' })}return (<div><button onClick={onChangeName}>修改 name 的值</button><div className="father"><!-- 通過 props 的數(shù)據(jù)綁定,把數(shù)據(jù)傳遞給子組件 --><Son1 {...state} /><Son2 {...state} /></div></div>)
}
2、在子組件中,指定 props
的類型為 React.FC<UserType>
,并使用 props
接收和渲染數(shù)據(jù):
// 子組件1
const Son1: React.FC<UserType> = (props) => {return (<div className="son1"><p>用戶信息:</p><p>{JSON.stringify(props)}</p></div>)
}// 子組件2
const Son2: React.FC<UserType> = (props) => {return (<div className="son2"><p>用戶信息:</p><p>{JSON.stringify(props)}</p></div>)
}
修改完成后,點(diǎn)擊父組件中的 button 按鈕修改用戶名,我們發(fā)現(xiàn)兩個(gè)子組件中的數(shù)據(jù)同步發(fā)生了變化。
7. 在子組件中實(shí)現(xiàn)點(diǎn)擊按鈕 age 自增操作
1、擴(kuò)充 ActionType
的類型如下:
// 定義 action 的類型
type ActionType = { type: 'UPDATE_NAME'; payload: string } | { type: 'INCREMENT'; payload: number }
2、在 reducer
中添加 INCREMENT
的 case
匹配:
const reducer = (prevState: UserType, action: ActionType) => {console.log('觸發(fā)了 reducer 函數(shù)', action)switch (action.type) {case 'UPDATE_NAME':return { ...prevState, name: action.payload }// 添加 INCREMENT 的 case 匹配case 'INCREMENT':return { ...prevState, age: prevState.age + action.payload }default:return prevState}
}
3、在子組件 Son1
中添加 +1
的 button
按鈕,并綁定點(diǎn)擊事件處理函數(shù):
// 子組件1
const Son1: React.FC<UserType> = (props) => {const add = () => {}return (<div className="son1"><p>用戶信息:</p><p>{JSON.stringify(props)}</p><button onClick={add}>+1</button></div>)
}
4、現(xiàn)在的問題是:子組件 Son1
中無(wú)法調(diào)用到父組件的 dispatch
函數(shù)。
為了解決這個(gè)問題,我們需要在 Father
父組件中,通過 props
把父組件中的 dispatch
傳遞給子組件:
// 父組件
export const Father: React.FC = () => {// useReducer(fn, 初始數(shù)據(jù), 對(duì)初始數(shù)據(jù)進(jìn)行處理的fn)const [state, dispatch] = useReducer(reducer, defaultState, initAction)const onChangeName = () => {dispatch({ type: 'UPDATE_NAME', payload: '劉龍彬' })}return (<div><button onClick={onChangeName}>修改 name 的值</button><div className="father"><Son1 {...state} dispatch={dispatch} /><Son2 {...state} /></div></div>)
}
5、在 Son1
子組件中,擴(kuò)充 React.FC<UserType>
的類型,并從 props
中把 dispatch
和用戶信息對(duì)象分離出來:
// 子組件1
const Son1: React.FC<UserType & { dispatch: React.Dispatch<ActionType> }> = (props) => {const { dispatch, ...user } = propsconst add = () => dispatch({ type: 'INCREMENT', payload: 1 })return (<div className="son1"><p>用戶信息:</p><p>{JSON.stringify(user)}</p><button onClick={add}>+1</button></div>)
}
8. 在子組件中實(shí)現(xiàn)點(diǎn)擊按鈕 age 自減操作
擴(kuò)充 ActionType 的類型如下:
// 定義 action 的類型
type ActionType = { type: 'UPDATE_NAME'; payload: string } | { type: 'INCREMENT'; payload: number } | { type: 'DECREMENT'; payload: number }
在 reducer 中添加 DECREMENT 的 case 匹配:
const reducer = (prevState: UserType, action: ActionType) => {console.log('觸發(fā)了 reducer 函數(shù)', action)switch (action.type) {case 'UPDATE_NAME':return { ...prevState, name: action.payload }case 'INCREMENT':return { ...prevState, age: prevState.age + action.payload }// 添加 DECREMENT 的 case 匹配case 'DECREMENT':return { ...prevState, age: prevState.age - action.payload }default:return prevState}
}
在子組件 Son2 中添加 -5 的 button 按鈕,并綁定點(diǎn)擊事件處理函數(shù):
// 子組件2
const Son2: React.FC<UserType> = (props) => {const sub = () => { }return (<div className="son2"><p>用戶信息:</p><p>{JSON.stringify(props)}</p><button onClick={sub}>-5</button></div>)
}
現(xiàn)在的問題是:子組件 Son2 中無(wú)法調(diào)用到父組件的 dispatch 函數(shù)。為了解決這個(gè)問題,我們需要在 Father 父組件中,通過 props 把父組件中的 dispatch 傳遞給子組件:
// 父組件
export const Father: React.FC = () => {// useReducer(fn, 初始數(shù)據(jù), 對(duì)初始數(shù)據(jù)進(jìn)行處理的fn)const [state, dispatch] = useReducer(reducer, defaultState, initAction)const onChangeName = () => {dispatch({ type: 'UPDATE_NAME', payload: '劉龍彬' })}return (<div><button onClick={onChangeName}>修改 name 的值</button><div className="father"><Son1 {...state} dispatch={dispatch} /><Son2 {...state} dispatch={dispatch} /></div></div>)
}
在 Son2 子組件中,擴(kuò)充 React.FC 的類型,并從 props 中把 dispatch 和用戶信息對(duì)象分離出來:
// 子組件2
const Son2: React.FC<UserType & { dispatch: React.Dispatch<ActionType> }> = (props) => {const { dispatch, ...user } = propsconst sub = () => dispatch({ type: 'DECREMENT', payload: 5 })return (<div className="son2"><p>用戶信息:</p><p>{JSON.stringify(user)}</p><button onClick={sub}>-5</button></div>)
}
9. 在 GrandSon 組件中實(shí)現(xiàn)重置按鈕
1、擴(kuò)充 ActionType
的類型如下:
// 定義 action 的類型
type ActionType = { type: 'UPDATE_NAME'; payload: string } | { type: 'INCREMENT'; payload: number } | { type: 'DECREMENT'; payload: number } | { type: 'RESET' }
2、在 reducer
中添加 RESET
的 case
匹配:
const reducer = (prevState: UserType, action: ActionType) => {console.log('觸發(fā)了 reducer 函數(shù)', action)switch (action.type) {case 'UPDATE_NAME':return { ...prevState, name: action.payload }case 'INCREMENT':return { ...prevState, age: prevState.age + action.payload }case 'DECREMENT':return { ...prevState, age: prevState.age - action.payload }// 添加 RESET 的 case 匹配case 'RESET':return defaultStatedefault:return prevState}
}
3、在 GrandSon
組件中,添加重置按鈕,并綁定點(diǎn)擊事件處理函數(shù):
const GrandSon: React.FC<{ dispatch: React.Dispatch<ActionType> }> = (props) => {const reset = () => props.dispatch({ type: 'RESET' })return (<><h3>這是 GrandSon 組件</h3><button onClick={reset}>重置</button></>)
}
10. 使用 Immer 編寫更簡(jiǎn)潔的 reducer 更新邏輯
解決每次重新為
對(duì)象/數(shù)組
解除引用的問題
1、安裝 immer
相關(guān)的依賴包:
npm install immer use-immer -S
2、從 use-immer
中導(dǎo)入 useImmerReducer
函數(shù),并替換掉 React
官方的 useReducer
函數(shù)的調(diào)用:
// 1. 導(dǎo)入 useImmerReducer
import { useImmerReducer } from 'use-immer'// 父組件
export const Father: React.FC = () => {// 2. 把 useReducer() 的調(diào)用替換成 useImmerReducer()const [state, dispatch] = useImmerReducer(reducer, defaultState, initAction)
}
3、修改 reducer
函數(shù)中的業(yè)務(wù)邏輯,case
代碼塊中不再需要 return
不可變的新對(duì)象了,只需要在 prevState
上進(jìn)行修改即可。
Immer 內(nèi)部會(huì)復(fù)制并返回新對(duì)象,因此降低了用戶的心智負(fù)擔(dān)。改造后的 reducer
代碼如下:
const reducer = (prevState: UserType, action: ActionType) => {console.log('觸發(fā)了 reducer 函數(shù)', action)switch (action.type) {case 'UPDATE_NAME':// return { ...prevState, name: action.payload }prevState.name = action.payloadbreakcase 'INCREMENT':// return { ...prevState, age: prevState.age + action.payload }prevState.age += action.payloadbreakcase 'DECREMENT':// return { ...prevState, age: prevState.age - action.payload }prevState.age -= action.payloadbreakcase 'RESET':return defaultStatedefault:return prevState}
}