携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
1.useReducer介绍
官网定义:useReducer是useState的替代方案,当数据处理较为复杂时,可以选择使用useReducer。许多小伙伴第一眼看到useReducer以为是Redux的方案,其实它和redux毛关系都没有。useReducer仅仅是数据响应式,不具备组件数据共享的特点。
2.使用
const [num,dispatch] = useReducer(fn,number)
useReducer的使用可以对比useState。其左边是一个数组,其中数组的第一个数据接收初始的数据,数组的第二个是一个函数,专门用来修改初始数据。这个特点和useState一模一样。 接下来是useReducer的右边。右边传入的第一个参数是自定义修改数据的函数,第二个参数是初始数据
import { useReducer } from "react"
//1.初始数据
let number = 1
//2.修改数据的函数
function reducer(state,action) {
if(action==='add') {
return state+1
} else if(action==='del') {
return state-1
}
return state
}
export default function Son(props) {
const [num,dispatch] = useReducer(reducer,number)
return <div>
<div>num:{num}</div>
<div onClick={()=>dispatch('add')}>+1</div>
<div onClick={()=>dispatch('del')}>-1</div>
</div>
}
效果
3.useReducer的复用性
当我们有多个组件或者多处需要同一份数据处理逻辑时,可以使用一份reducer实现。记住仅仅是功能上复用,不存在数据共享的特点。如下例子
App.js
app组件引入了组件son1和son2。其中son1组件和son2组件引入同一份数据和修改数据的方法
import Son1 from "./views/Son"
import Son2 from "./views/Son1"
function App() {
return <div>
<Son1/>
<div>------------------</div>
<Son2/>
</div>
}
export default App;
common.js
保存数据和修改数据的逻辑
export let number = 1
export function reducer(state,action) {
if(action==='add') {
return state+1
} else if(action==='del') {
return state-1
}
return state
Son1组件
import { useReducer } from "react"
import {number,reducer} from "./common"
export default function Son1(props) {
const [num,dispatch] = useReducer(reducer,number)
return <div>
<div>num:{num}</div>
<div onClick={()=>dispatch('add')}>+1</div>
<div onClick={()=>dispatch('del')}>-1</div>
</div>
}
Son2组件
import { useReducer } from "react"
import {number,reducer} from "./common"
export default function Son2(props) {
const [num,dispatch] = useReducer(reducer,number)
return <div>
<div>num:{num}</div>
<div onClick={()=>dispatch('add')}>+1</div>
<div onClick={()=>dispatch('del')}>-1</div>
</div>
}
通过上述代码发现,son1组件和son2组件使用相同的数据,并且使用相同的reducer处理函数。 观察页面显示,二者页面数据修改并不会影响其它页面数据。因此useReducer不具备数据共享功能。只是useState的替代方案。
4.考虑如何模拟组件数据共享(类似redux)
方案一:使用useState+父子数据通信
在根组件上定义useState定义初始数据,然后使用父组件给儿子后代传递一个函数,这个函数可以用来通知父组件修改数据。
App.js
import Son1 from "./views/Son"
import Son2 from "./views/Son1"
import {useState} from "react"
function App() {
//全局数据
const [num,setNum] = useState(1)
//全局修改数据
function changeFn(number) {
setNum(num+number)
}
return <div className="app">
<Son1 num={num} change = {changeFn} />
<div>-------------------</div>
<Son2 num={num} change = {changeFn}/>
</div>
}
export default App;
Son1.js
export default function Son1(props) {
return <div>
<div>num:{props.num}</div>
<div onClick={()=>props.change(1)}>+1</div>
<div onClick={()=>props.change(-1)}>-1</div>
</div>
}
Son2.js
export default function Son2(props) {
return <div>
<div>num:{props.num}</div>
<div onClick={()=>props.change(1)}>+1</div>
<div onClick={()=>props.change(-1)}>-1</div>
</div>
}
效果
我们可以看到此时的组件son1和son2是可以共享全局数据的,任意一处数据的修改,其它组件也会同步修改数据。但是自己考虑,如果组件的层级增加到3层或者4层?那么每个组件都要向下传递函数,此时维护也变得复杂。
方案二:useState+useContext
组件结构图
可以看到根组件包含两个儿子组件son1和son2.而son1组件又包含一个子组件son3
方案思路
根组件app.js使用useState添加全局数据,并且定义修改state的函数。使用useContext可以传递数据的特性,将state数据和修改state的函数传递给所有的后代组件。后代组件可以获取state数据,也可以接收到修改state的函数。
App.js
import Son1 from "./views/Son"
import Son2 from "./views/Son1"
import {useState,createContext} from "react"
export const Context = createContext()
function App() {
//1.全局数据
const [num,setNum] = useState(1)
//2.全局修改数据的函数
function changeFn(number) {
setNum(num+number)
}
return <div className="app">
//3.使用useContext将数据和函数传递给后代
<Context.Provider value={{num:num,changeFn:changeFn}}>
<Son1/>
<div>-------------------</div>
<Son2 />
</Context.Provider>
</div>
}
export default App;
Son1.js
import {Context} from "./../App.js"
import { useContext } from "react"
import Son3 from "./Son3.js"
export default function Son1() {
const context = useContext(Context)
return <div>
<div>num1:{context.num}</div>
<div onClick={()=>context.changeFn(1)}>+1</div>
<div onClick={()=>context.changeFn(-1)}>-1</div>
<div>-----</div>
<Son3/>
</div>
}
Son2.js
import {Context} from "./../App.js"
import { useContext } from "react"
export default function Son2() {
const context = useContext(Context)
return <div>
<div>num2:{context.num}</div>
<div onClick={()=>context.changeFn(1)}>+1</div>
<div onClick={()=>context.changeFn(-1)}>-1</div>
</div>
}
Son3.js
import {Context} from "./../App.js"
import { useContext } from "react"
export default function Son3() {
const context = useContext(Context)
return <div>
<div>num3:{context.num}</div>
<div onClick={()=>context.changeFn(1)}>+1</div>
<div onClick={()=>context.changeFn(-1)}>-1</div>
</div>
}
效果图
可以发现,任意一处组件值的修改,都可以做到全局修改,成功实现了组件数据共享和传递。
方案三 useReducer+useContext
既然useState+useContext可以完成数据共享,那作为useState的替代品,我们当然可以使用useContext+useReducer实现相同的数据共享。
组件结构图
设计方案
根组件App使用useReducer创建全局数据和修改全局数据的函数,使用useContext将数据和修改的函数传递给后代组件。后代组件接收并使用。
App.js
import Son1 from "./views/Son"
import Son2 from "./views/Son1"
import "./App.css"
import {createContext,useReducer} from "react"
//1.全局数据
let num = 1
//2.全局修改数据
function reducer(state,action) {
if(action==='add') {
return state+1
} else if(action==='del') {
return state-1
}
return state
}
//3.定义context传递数据和函数给后代
export const Context = createContext()
function App() {
const [number,dispatch] = useReducer(reducer,num)
return <div className="app">
<Context.Provider value={{number:number,dispatch:dispatch}}>
<Son1/>
<div>-------------------</div>
<Son2 />
</Context.Provider>
</div>
}
export default App;
Son1.js
import {Context} from "./../App.js"
import { useContext } from "react"
import Son3 from "./Son3.js"
export default function Son1() {
const context = useContext(Context)
return <div>
<div>son1:{context.number}</div>
<div onClick={()=>context.dispatch('add')}>+1</div>
<div onClick={()=>context.dispatch('del')}>-1</div>
<div>------</div>
<Son3/>
</div>
}
Son2.js
import {Context} from "./../App.js"
import { useContext } from "react"
export default function Son2() {
const context = useContext(Context)
return <div>
<div>son2:{context.number}</div>
<div onClick={()=>context.dispatch('add')}>+1</div>
<div onClick={()=>context.dispatch('del')}>-1</div>
</div>
}
Son3.js
import {Context} from "./../App.js"
import { useContext } from "react"
export default function Son3() {
const context = useContext(Context)
return <div>
<div>son3:{context.number}</div>
<div onClick={()=>context.dispatch('add')}>+1</div>
<div onClick={()=>context.dispatch('del')}>-1</div>
</div>
}
效果图
总结
- useReducer是useState的替代品,二者都是为了完成数据响应式
- 当数据是简单数据,如Number,boolean.string或者简单对象时,优先用useState完成数据响应式 而数据是复杂对象,并且数据修改较为繁杂时考虑使用useReducer实现数据响应式
- 当数据的修改存在多个组件复用时,可以考虑使用useReducer完成数据抽离和响应式
- useState+useContext或者useReducer+useContext可以轻松实现类似Reducer的全局数据共享