与 useReducer
相比,我很难理解 useState
何时以及为何具有优势。那里有很多争论,但对我来说,没有一个是有意义的,在这篇文章中,我试图将它们应用到一个简单的例子中。
也许我遗漏了一些东西,但我不明白为什么应该在 useReducer
的任何地方使用 useState
。我希望你能帮助我澄清这一点。
让我们看这个例子:
版本 A - 带有 useState
function CounterControls(props) {
return (
<>
<button onClick={props.increment}>increment</button>
<button onClick={props.decrement}>decrement</button>
</>
);
}
export default function App() {
const [complexState, setComplexState] = useState({ nested: { deeply: 1 } });
function increment() {
setComplexState(state => {
// do very complex logic here that depends on previous complexState
state.nested.deeply += 1;
return { ...state };
});
}
function decrement() {
setComplexState(state => {
// do very complex logic here that depends on previous complexState
state.nested.deeply -= 1;
return { ...state };
});
}
return (
<div>
<h1>{complexState.nested.deeply}</h1>
<CounterControls increment={increment} decrement={decrement} />
</div>
);
}
看到这个 stackblitz
版本 B - 使用 useReducer
import React from "react";
import { useReducer } from "react";
function CounterControls(props) {
return (
<>
<button onClick={() => props.dispatch({ type: "increment" })}>
increment
</button>
<button onClick={() => props.dispatch({ type: "decrement" })}>
decrement
</button>
</>
);
}
export default function App() {
const [complexState, dispatch] = useReducer(reducer, {
nested: { deeply: 1 }
});
function reducer(state, action) {
switch (action.type) {
case "increment":
state.nested.deeply += 1;
return { ...state };
case "decrement":
state.nested.deeply -= 1;
return { ...state };
default:
throw new Error();
}
}
return (
<div>
<h1>{complexState.nested.deeply}</h1>
<CounterControls dispatch={dispatch} />
</div>
);
}
看到这个 stackblitz
在很多文章(包括 docs )中,两个论点似乎很流行:
“useReducer 适用于复杂的状态逻辑”。 在我们的示例中,假设 complexState
很复杂,有很多修改操作,每个操作都有很多逻辑。 useReducer
在这里有什么帮助?对于复杂的状态,拥有单独的函数而不是拥有一个 200 行的 reducer 函数甚至不是 better
吗?
“如果下一个状态依赖于前一个状态,useReducer 就很好”。 我可以用 useState 做同样的事情,不是吗?只需写 setState(oldstate => {...})
潜在的其他优势:
- “我不必传递多个函数,而只传递一个 reducer”:好的,但我也可以使用 useCallback 等将我的函数包装到一个“动作”对象中。正如已经提到的,在不同的函数中具有不同的逻辑似乎对我来说是件好事。
- “我可以为 reducer 提供一个上下文,这样我的复杂状态就可以在整个应用程序中轻松修改”。是的,但是您也可以从该上下文中提供单个函数(可能由 useCallback 包装)
我看到的缺点:
- 单个超长函数中的多个不同动作似乎令人困惑
- 更容易出错,因为您必须检查 reducer 函数或依赖打字稿等来找出可以传递给 reducer 的字符串以及附带的参数。调用函数时,这要简单得多。
考虑到这一切:你能给我一个很好的例子,说明 useReducer
真的很出色,并且不能轻易地用 useState
重写为一个版本吗?
几个月后,我觉得我必须为这个话题添加一些见解。如果在
useReducer
和useState
之间进行选择只是个人喜好问题,为什么人们会写这样的东西:Dan Abramov 上的 twitter :
React docs
React docs :
因此,让我们尝试确定并找到一个场景,其中
useReducer
明显优于useState
:如果需要从嵌套组件中的
useEffect
调用更新函数怎么办?VersionA 的方法(
useState
& 传递回调)可能会遇到问题:App
的每次渲染中!useCallback
会有所帮助,但这种模式很快就会变得乏味,尤其是当我们需要在useMemo
对象上额外调用actions
时。 (我也不是这方面的专家,但从性能的角度来看,这听起来不太令人信服)useCallback
也无济于事。如果我们改用减速器:
dispatch
函数始终具有稳定的标识! (见 react docs )同样,参见 Dan Abramov 的 Twitter Post :
实际例子
在这段代码中,我试图强调使用
useReducer
的一些优势,我之前试图描述过这些优势:看到这个 codesandbox
dispatch
的身份保持不变!不会触发效果