可以做图片视频的网站,深圳网络设计,wordpress文章保存图片不显示,商业网站缩写useReducer
usereducer 相当于 复杂的 useState 当状态更新逻辑较复杂时可以考虑使用 useReducer。useReducer 可以同时更新多个状态#xff0c;而且能把对状态的修改从组件中独立出来。
相比于 useState#xff0c;useReducer 可以更好的描述“如何更新状态”。例如#…useReducer
usereducer 相当于 复杂的 useState 当状态更新逻辑较复杂时可以考虑使用 useReducer。useReducer 可以同时更新多个状态而且能把对状态的修改从组件中独立出来。
相比于 useStateuseReducer 可以更好的描述“如何更新状态”。例如组件负责发出行为useReducer 负责更新状态。
好处是让代码逻辑更清晰代码行为更易预测。
1. useReducer 的语法格式
useReducer 的基础语法如下
const [state, dispatch] useReducer(reducer, initState, initAction?)其中
1. reducer 是一个函数类似于 (prevState, action) newState。形参 prevState 表示旧状态形参 action 表示本次的行为返回值 newState 表示处理完毕后的新状态。
2. initState 表示初始状态也就是默认值。
3. initAction 是进行状态初始化时候的处理函数它是可选的如果提供了 initAction 函数则会把 initState 传递给 initAction 函数进行处理initAction 的返回值会被当做初始状态。
4. 返回值 state 是状态值。dispatch 是更新 state 的方法让他接收 action 作为参数useReducer 只需要调用 dispatch(action) 方法传入的 action 即可更新 state。
2. 定义组件的基础结构
定义名为 Father 的父组件如下
import React from react// 父组件
export const Father: React.FC () {return (divbutton修改 name 的值/buttondiv classNamefatherSon1 /Son2 //div/div)
}定义名为 Son1 和 Son2 的两个子组件如下
// 子组件1
const Son1: React.FC () {return div classNameson1/div
}// 子组件2
const Son2: React.FC () {return div classNameson2/div
}在 index.css 中添加对应的样式
.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 的基础结构
1、按需导入 useReducer 函数
import React, { useReducer } from react2、定义初始数据
const defaultState { name: liulongbin, age: 16 }3、定义 reducer 函数它的作用是根据旧状态进行一系列处理最终返回新状态
// 第一个参数永远是上一次的旧状态
const reducer (prevState) {// 首次进入页面 不会触发 reducer 函数执行console.log(触发了 reducer 函数)// 必须向外返回一个处理好的新状态 return prevState
}4、在 Father 组件中调用 useReducer(reducerFn, 初始状态) 函数并得到 reducer 返回的状态
// 父组件
export const Father: React.FC () {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state] useReducer(reducer, defaultState)console.log(state)return (divbutton修改 name 的值/buttondiv classNamefatherSon1 /Son2 //div/div)
}5、为 reducer 中的 initState 指定数据类型
// 定义状态的数据类型
type UserType typeof defaultStateconst defaultState { name: liulongbin, age: 16 }// 给 initState 指定类型为 UserType
const reducer (prevState: UserType) {console.log(触发了 reducer 函数)return prevState
}6、接下来在 Father 组件中使用 state 时就可以出现类型的智能提示啦
// 父组件
export const Father: React.FC () {const [state] useReducer(reducer, defaultState)console.log(state.name, state.age)return (divbutton修改 name 的值/buttondiv classNamefatherSon1 /Son2 //div/div)
}4. 使用 initAction 处理初始数据
定义名为 initAction 的处理函数如果初始数据中的 age 为小数、负数、或 0 时对 age 进行非法值的处理
const initAction (initState: UserType) {// 把 return 的对象作为 useReducer 的初始值return { ...initState, age: Math.round(Math.abs(initState.age)) || 18 }
}在 Father 组件中使用步骤1声明的 initAction 函数如下
// 父组件
export const Father: React.FC () {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state] useReducer(reducer, defaultState, initAction)// 省略其它代码...
}在定义 defaultState 时为 age 提供非法值可以看到非法值在 initAction 中被处理掉了。 5. 在 Father 组件中点击按钮修改 name 的值
1. 错误示范
不要像 vue 响应式数据一样直接通过 state.name escook 去修改
// 父组件
export const Father: React.FC () {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state] useReducer(reducer, defaultState, initAction)console.log(state)const onChangeName () {// 注意这种用法是错误的因为不能【直接修改 state 的值】// 因为存储在 useReducer 中的数据都是“不可变”的// 要想修改 useReducer 中的数据必须触发 【reducer】 函数的重新计算// 根据 reducer 形参中的旧状态对象initState经过一系列处理返回一个“全新的”状态对象state.name escook}return (divbutton onClick{onChangeName}修改 name 的值/buttondiv classNamefatherSon1 /Son2 //div/div)
}2. 正确的操作
为了能够触发 reducer 函数的重新执行我们需要在调用 useReducer() 后接收返回的 dispatch 函数。示例代码如下
// Father 父组件
const [state, dispatch] useReducer(reducer, defaultState, initAction)在 button 按钮的点击事件处理函数中调用 dispatch() 函数从而触发 reducer 函数的重新计算
// Father 父组件
const onChangeName () { dispatch()
}点击 Father 组件中如下的 button 按钮
button onClick{onChangeName}修改 name 的值/button会触发 reducer 函数的重新执行并打印 reducer 中的 console.log()代码如下
const reducer (prevState: UserType) {console.log(触发了 reducer 函数)return prevState
}3. 调用 dispatch 传递参数给 reducer
在 Father 父组件按钮的点击事件处理函数 onChangeName 中调用 dispatch() 函数并把参数传递给 reducer 的第2个形参代码如下
const onChangeName () {// 注意参数的格式为 { type, payload? }// 其中// type 的值是一个唯一的标识符用来【指定本次操作的类型】一般为大写的字符串// payload 是本次操作需要用到的数据为可选参数。在这里payload 指的是把用户名改为字符串 刘龙彬dispatch({type: UPDATE_NAME, payload: 刘龙彬})
}修改 reducer 函数的形参添加名为 action 的第2个形参用来接收 dispatch 传递过来的数据
const reducer (prevState: UserType, action) {// 打印 action 的值终端显示的值为// {type: UPDATE_NAME, payload: 刘龙彬}console.log(触发了 reducer 函数, action)return prevState
}在 reducer 中根据接收到的 action.type 标识符决定进行怎样的更新操作最终 return 一个计算好的新状态。示例代码如下
const reducer (prevState: UserType, action) {console.log(触发了 reducer 函数, action)// return prevStateswitch (action.type) {// 如果标识符是字符串 UPDATE_NAME则把用户名更新成 action.payload 的值// 最后一定要返回一个新状态因为 useReducer 中每一次的状态都是“不可变的”case UPDATE_NAME:return { ...prevState, name: action.payload } // 解除引用赋予新对象// 兜底操作// 如果没有匹配到任何操作则默认返回上一次的旧状态default:return prevState}
}4、为 action 指定类型
在上述的 switch...case... 代码期间没有 TS 的类型提示这在大型项目中是致命的。因此我们需要为 reducer 函数的第2个形参 action 指定操作的类型
// 1. 定义 action 的类型
type ActionType { type: UPDATE_NAME; payload: string }// 2. 为 action 指定类型为 ActionType
const reducer (prevState: UserType, action: ActionType) {console.log(触发了 reducer 函数, action)// 3. 删掉之前的代码再重复编写这段逻辑的时候会出现 TS 的类型提示非常 Niceswitch (action.type) {case UPDATE_NAME:return { ...prevState, name: action.payload }default:return prevState}
}同时在 Father 组件的 onChangeName 处理函数内调用 dispatch() 时也有了类型提示
const onChangeName () {dispatch({ type: UPDATE_NAME, payload: 刘龙彬 })
}注意在今后的开发中正确的顺序是 1、先定义 ActionType 的类型 2、修改 reducer 中的 switch...case... 逻辑 3、在组件中调用 dispatch() 函数这样能够充分利用 TS 的类型提示。 6. 把用户信息渲染到子组件中
1、在 Father 父组件中通过jsx展开语法把 state 数据对象绑定为 Son1 和 Son2 的 props 属性
// 父组件
export const Father: React.FC () {const [state, dispatch] useReducer(reducer, defaultState, initAction)const onChangeName () {dispatch({ type: UPDATE_NAME, payload: 刘龙彬 })}return (divbutton onClick{onChangeName}修改 name 的值/buttondiv classNamefather!-- 通过 props 的数据绑定把数据传递给子组件 --Son1 {...state} /Son2 {...state} //div/div)
}2、在子组件中指定 props 的类型为 React.FCUserType并使用 props 接收和渲染数据
// 子组件1
const Son1: React.FCUserType (props) {return (div classNameson1p用户信息/pp{JSON.stringify(props)}/p/div)
}// 子组件2
const Son2: React.FCUserType (props) {return (div classNameson2p用户信息/pp{JSON.stringify(props)}/p/div)
}修改完成后点击父组件中的 button 按钮修改用户名我们发现两个子组件中的数据同步发生了变化。 7. 在子组件中实现点击按钮 age 自增操作
1、扩充 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(触发了 reducer 函数, 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 按钮并绑定点击事件处理函数
// 子组件1
const Son1: React.FCUserType (props) {const add () {}return (div classNameson1p用户信息/pp{JSON.stringify(props)}/pbutton onClick{add}1/button/div)
}4、现在的问题是子组件 Son1 中无法调用到父组件的 dispatch 函数。
为了解决这个问题我们需要在 Father 父组件中通过 props 把父组件中的 dispatch 传递给子组件
// 父组件
export const Father: React.FC () {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state, dispatch] useReducer(reducer, defaultState, initAction)const onChangeName () {dispatch({ type: UPDATE_NAME, payload: 刘龙彬 })}return (divbutton onClick{onChangeName}修改 name 的值/buttondiv classNamefatherSon1 {...state} dispatch{dispatch} /Son2 {...state} //div/div)
}5、在 Son1 子组件中扩充 React.FCUserType 的类型并从 props 中把 dispatch 和用户信息对象分离出来
// 子组件1
const Son1: React.FCUserType { dispatch: React.DispatchActionType } (props) {const { dispatch, ...user } propsconst add () dispatch({ type: INCREMENT, payload: 1 })return (div classNameson1p用户信息/pp{JSON.stringify(user)}/pbutton onClick{add}1/button/div)
}8. 在子组件中实现点击按钮 age 自减操作
扩充 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(触发了 reducer 函数, 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 按钮并绑定点击事件处理函数
// 子组件2
const Son2: React.FCUserType (props) {const sub () { }return (div classNameson2p用户信息/pp{JSON.stringify(props)}/pbutton onClick{sub}-5/button/div)
}现在的问题是子组件 Son2 中无法调用到父组件的 dispatch 函数。为了解决这个问题我们需要在 Father 父组件中通过 props 把父组件中的 dispatch 传递给子组件
// 父组件
export const Father: React.FC () {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state, dispatch] useReducer(reducer, defaultState, initAction)const onChangeName () {dispatch({ type: UPDATE_NAME, payload: 刘龙彬 })}return (divbutton onClick{onChangeName}修改 name 的值/buttondiv classNamefatherSon1 {...state} dispatch{dispatch} /Son2 {...state} dispatch{dispatch} //div/div)
}在 Son2 子组件中扩充 React.FC 的类型并从 props 中把 dispatch 和用户信息对象分离出来
// 子组件2
const Son2: React.FCUserType { dispatch: React.DispatchActionType } (props) {const { dispatch, ...user } propsconst sub () dispatch({ type: DECREMENT, payload: 5 })return (div classNameson2p用户信息/pp{JSON.stringify(user)}/pbutton onClick{sub}-5/button/div)
}9. 在 GrandSon 组件中实现重置按钮
1、扩充 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(触发了 reducer 函数, 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 组件中添加重置按钮并绑定点击事件处理函数
const GrandSon: React.FC{ dispatch: React.DispatchActionType } (props) {const reset () props.dispatch({ type: RESET })return (h3这是 GrandSon 组件/h3button onClick{reset}重置/button/)
}10. 使用 Immer 编写更简洁的 reducer 更新逻辑 解决每次重新为 对象/数组 解除引用的问题 1、安装 immer 相关的依赖包
npm install immer use-immer -S2、从 use-immer 中导入 useImmerReducer 函数并替换掉 React 官方的 useReducer 函数的调用
// 1. 导入 useImmerReducer
import { useImmerReducer } from use-immer// 父组件
export const Father: React.FC () {// 2. 把 useReducer() 的调用替换成 useImmerReducer()const [state, dispatch] useImmerReducer(reducer, defaultState, initAction)
}3、修改 reducer 函数中的业务逻辑case 代码块中不再需要 return 不可变的新对象了只需要在 prevState 上进行修改即可。
Immer 内部会复制并返回新对象因此降低了用户的心智负担。改造后的 reducer 代码如下
const reducer (prevState: UserType, action: ActionType) {console.log(触发了 reducer 函数, 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}
}