当您具有涉及多个子值的复杂状态逻辑时,或者当下一个状态依赖于前一个状态时,useReducer通常比useState更可取。 useReducer还可让您优化触发深度更新的组件的性能,因为您可以传递调度而不是回调。
(引用https://reactjs.org/docs/hooks-reference.html#usereducer)
我对粗体部分感兴趣,该部分指出在上下文中使用useReducer
而不是useState
。
我尝试了两种变体,但它们似乎没有什么不同。
我比较这两种方法的方式如下:
const [state, updateState] = useState();
const [reducerState, dispatch] = useReducer(myReducerFunction);
我将它们每个都传递给一个上下文对象,该上下文对象在一个更深的孩子中使用(我只是运行了单独的测试,将值替换为要测试的函数)。
<ContextObject.Provider value={updateState // dispatch}>
孩子包含这些功能
const updateFunction = useContext(ContextObject);
useEffect(
() => {
console.log('effect triggered');
console.log(updateFunction);
},
[updateFunction]
);
在这两种情况下,当父对象重新渲染时(由于另一个局部状态更改),效果永远不会运行,这表明更新功能在渲染之间未更改。 我读引号中的粗体字是否错了?还是我忽略了什么?
答案 0 :(得分:3)
useReducer还可让您优化组件的性能, 触发深度更新,因为您可以传递调度而不是传递 回调。
上面的语句并不是要表明useState
返回的setter是在每次更新或渲染时新创建的。意思是,当您有复杂的逻辑来更新状态时,您根本不会直接使用setter来更新状态,而是您将编写一个复杂的函数,该函数会依次调用具有更新状态的setter之类的
const handleStateChange = () => {
// lots of logic to derive updated state
updateState(newState);
}
ContextObject.Provider value={{state, handleStateChange}}>
在上述情况下,每次重新渲染父对象时,都会创建一个新的handleStateChange实例,从而使Context Consumer也重新渲染。
上述情况的一种解决方案是使用useCallback
并记住状态更新程序方法并使用它。但是,为此,您需要解决与使用方法中的值相关的关闭问题。
因此,建议使用useReducer
,该方法返回在重新渲染之间不变的dispatch
方法,并且可以在化简器中使用操作逻辑。
答案 1 :(得分:0)
如果您在render上创建一个回调并将其传递给子组件,则该子组件的属性将更改。但是,当父级渲染时,常规组件将重新渲染(到虚拟dom),甚至道具也保持不变。例外是实现了shouldComponentUpdate并比较属性(例如PureComponent)的classComponent。
这是一种优化,只有在重新渲染子组件需要大量计算时(如果多次在同一屏幕上渲染它,或者需要进行深度渲染或大量重新渲染),您才应该关心它。
如果是这种情况,则应确保: 1.您的孩子是扩展PureComponent的类组件 2.避免将新创建的函数作为道具传递。而是通过 分派,从React.useState返回的setter或备忘录 定制的二传手。
尽管我不建议为特定组件构建唯一的备注设置器(需要注意一些事项),但是您可以使用通用挂钩来为您实现。
这里是useObjState钩子的示例,该钩子提供了简单的API,并且不会引起其他折旧。
const useObjState = initialObj => {
const [obj, setObj] = React.useState(initialObj);
const memoizedSetObj = React.useMemo(() => {
const helper = {};
Object.keys(initialObj).forEach(key => {
helper[key] = newVal =>
setObj(prevObj => ({ ...prevObj, [key]: newVal }));
});
return helper;
}, []);
return [obj, memoizedSetObj];
};
function App() {
const [user, memoizedSetUser] = useObjState({
id: 1,
name: "ed",
age: null,
});
return (
<NameComp
setter={memoizedSetUser.name}
name={user.name}
/>
);
}
const NameComp = ({name, setter}) => (
<div>
<h1>{name}</h1>
<input
value={name}
onChange={e => setter(e.target.value)}
/>
</div>
)