假设我实现了一个简单的全局加载状态,如下所示:
// hooks/useLoading.js
import React, { createContext, useContext, useReducer } from 'react';
const Context = createContext();
const { Provider } = Context;
const initialState = {
isLoading: false,
};
function reducer(state, action) {
switch (action.type) {
case 'SET_LOADING_ON': {
return {
...state,
isLoading: true,
};
}
case 'SET_LOADING_OFF': {
return {
...state,
isLoading: false,
};
}
}
}
export const actionCreators = {
setLoadingOn: () => ({
type: 'SET_LOADING_ON',
}),
setLoadingOff: () => ({
type: 'SET_LOADING_OFF',
}),
};
export const LoadingProvider = ({ children }) => {
const [{ isLoading }, dispatch] = useReducer(reducer, initialState);
return <Provider value={{ isLoading, dispatch }}>{children}</Provider>;
};
export default () => useContext(Context);
然后假设我有一个使加载状态发生变化但从不消耗它的组件,如下所示:
import React from 'react';
import useLoading, { actionCreators } from 'hooks/useLoading';
export default () => {
const { dispatch } = useLoading();
dispatch(actionCreators.setLoadingOn();
doSomethingAsync().then(() => dispatch(actionCreators.setLoadingOff()))
return <React.Fragment />;
};
根据useReducer文档,调度具有稳定的身份。我的解释是,当组件从useReducer中提取调度时,与该调度相关的状态更改时,它将不会重新渲染,因为对调度的引用将始终相同。基本上,分派可以“视为静态值”。
但是,当运行此代码时,该行将dispatch(actionCreators.setLoadingOn())
触发对全局状态的更新,并且该useLoading
钩子将再次运行dispatch(actionCreators.setLoadingOn())
(无限重新渲染-_-)
我不是正确理解useReducer吗?还是我正在做的其他事情可能导致无限重新渲染?
第一个问题是,你应该永远不会触发任何阵营状态更新,同时呈现,包括useReducers
的dispatch()
和useState
的制定者。
第二个问题是,是的,在分派的同时总是使React排队状态更新并尝试调用化简器,如果化简器返回新值,React将继续重新渲染。不管您从哪个组件派遣了什么-首先要引起状态更新和重新渲染useReducer
。
“稳定的身份”表示dispatch
变量将跨渲染指向相同的函数引用。
无疑,这更多是出于演示目的。如果这是一个按钮,也许是一个更现实,更现实的示例。也许像这样:onClick = {()=> dispatch(actionCreators.setLoadingOn())}除了细节,在较高的层次上,我们将拥有的是一个纯功能组件,该组件可以改变某些状态。但是根据钩子规则,这样的组件会在每次状态更改时重新呈现,即使它不订阅它所突变的任何状态。当然,我可以使用诸如useMemo之类的东西来控制此组件的重新渲染规则,但是仍然可以。好像很奇怪
不知道您在这里指的是什么问题。请记住,React的默认行为是始终对每个状态更改进行递归重新渲染。
useReducer
在这方面没有什么特别的-它只是在组件中组织状态更新逻辑的另一种机制。是的,我现在看到了。谢谢。现在我很好奇有什么优势
useReducer
了useState
。当我仅使用useState
和来实现全局状态时Context
,我将通过全局状态钩子传递getter和setter回调。我认为这对于只使用setter回调的组件是不好的做法,因为这会导致不必要的重新渲染。我以为“身份安全”调度可以解决此问题,但事实并非如此。如果子组件正尝试通过道具比较(例如和)优化重新渲染,则
dispatch
和设置者的稳定身份很有用。总的来说,如果您具有复杂的状态更新逻辑,需要避免必须读取当前状态以计算新状态的闭包,或者希望让子组件仅指示“某些事件已发生”并使逻辑保持较高状态,则很有用。React.memo()
PureComponent
useReducer