useReducer 是在 react V 16.8 推出的钩子函数,从用法层面来说是可以代替useState。众所周知,useState 常用在单个组件中进行状态管理,但是遇到状态全局管理的时候,useState 显然不能满足我们的需求,这个时候大多数的做法是利用第三方的状态管理工具,像 redux,Recoil 或者 Mobx。
import XXX from Mobx;
import XXX from Redux; // or import XXX from Recoil;
强大的 React 团队难道就不能自己实现一个全局的状态管理的 hook 吗,这不,useReducer 为了解决这个需求应运而生。
useReducer 钩子用来存储和更新状态,有点类似 useState 钩子。在用法上,它接收一个reducer函数作为第一个参数,第二个参数是初始化的state。useReducer最终返回一个存储有当前状态值和dispatch函数的数组,该dispatch函数触发的时,会调用reducer的方法,reducer方法返回的值会更新state
const [count, dispatch] = useReducer(reducer, initialState);
1、当多个 state 需要一起更新时
2、当state 更新逻辑较复杂
3、当下一个 state 依赖于之前的 state,即 编写 setState(prevState => newState)时
包括但不限于以上三种
1、useReducer 相对于 useState 可以更好的描述“如何更新状态”。 比如:useReducer 能够读取相关的状态、同时更新多个状态。
2、组件负责发出 action,reducer 负责更新状态的模式, 使得代码逻辑更加清晰。代码行为更加可以预测(比useEffect 的更新时机更加稳定)
3、通过传递 dispatch ,可以减少状态值的传递。 useReducer 总是返回相同的 dispatch 函数,这是彻底解耦的标志:状态更新逻辑可以任意变化,而发起 action 的渠道始终不变。
每秒对输入框里输入的数值进行累加的操作
import { useEffect, useState } from "react";
export default () => {
const [num, setNum] = useState(0);
const [inputVal, setInputVal] = useState(1);
useEffect(() => {
const ter = setInterval(() => {
console.log("一直不变的num", num)
// 当前数据的更新依赖上一次state的状态
setNum((prevNum) => {
console.log("最新的值num", prevNum)
return prevNum + inputVal
});
}, 1000);
return () => {
clearInterval(ter);
};
/**
* 还要考虑什么时候更新闭包里面的数据
*
* 为什么已经有更新闭包数据了,还需要拿num上一次的数据呢?
* 我们更新的数据是基于每次inputVal状态变的时候更新,所以我们能拿到
* 最新的inputVal,但是num可能拿不到最新的
*/
}, [inputVal]);
return (
{num}
setInputVal(Number(e.target.value))}
/>
);
};
useReducer()方法使得组件只需要发出action,而无需知道如何更新状态。另外,此时 step 的更新不会造成 useEffect 的失效、重执行。
import { useEffect, useReducer } from "react";
export default () => {
const reducer = (state, action) => {
switch (action.type) {
case "addNum":
return { ...state, num: state.num + state.inputVal };
case "inputChange":
return { ...state, ...action.payload };
default:
console.warn("当前没有可执行逻辑,请检查代码")
return state
}
};
const [state, dispatch] = useReducer(reducer, {
num: 0,
inputVal: 1,
});
useEffect(() => {
setInterval(() => {
dispatch({
type: "addNum",
});
}, 1000);
}, []);
console.log(state)
return (
<div>
{state.num}
<div>
<input
type="number"
value={state.inputVal}
onChange={(e) =>
dispatch({
type: "inputChange",
payload: {
inputVal: Number(e.target.value),
},
})
}
/>
</div>
</div>
);
};
试想一下,如果想实现以下组件需求:
1、父组件中定义某变量xx;
2、任何层级下的子组件都可以轻松获取变量xx、并且可以“修改”变量xx;
3、同级之间可以任意传值
注意
这里的修改是加引号的,因为事实上你永远无法以直接赋值的方式进行修改,永远都需要调用父级组件提供的方法来修改。
实现思路
用 useContext 实现“获取全局数据”
用 useReducer 实现“修改全局数据”
import { useReducer } from "react";
import AComponent from "./a";
import BComponent from "./b";
import MyContext from "./context";
/**
*
* 儿子可以直接拿到爷爷的状态以及新修改他的状态
*/
export default function LearnUseReducer4() {
const reducer = (state, action) => {
switch (action.type) {
case "add":
return { ...state, num: state.num + 1 };
case "childAddNum":
return { ...state, childNum: state.childNum + 1 };
}
};
const [state, dispatch] = useReducer(reducer, {
num: 1,
childNum: 1,
});
return (
爷爷
);
}
import { useContext, useState } from "react";
import CComponent from "./c";
import MyContext from "./context";
export default function AComponent() {
const context = useContext(MyContext);
/**
* 希望把这个num属性传递给叔叔这个组件
* 那么首页要把num属性进行状态提升
*/
// const [num, setNum] = useState(1)
return (
爸爸 -- {context.state.childNum}
{/* */}
);
}
import MyContext from "./context";
import { useContext } from "react";
export default function BComponent() {
const context = useContext(MyContext);
return (
叔叔 --- {context.state.childNum}
);
}
import { useContext } from "react";
import MyContext from "./context";
export default function CComponent() {
const context = useContext(MyContext);
return (
儿子 --- {context.state.num}
);
}
ext = useContext(MyContext);
return (
儿子 --- {context.state.num}
);
}