为什么 useReducer 的调度会导致重新渲染?

IT技术 javascript reactjs redux react-hooks use-reducer
2021-05-26 13:15:51

假设我实现了一个简单的全局加载状态,如下所示:

// 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 文档,dispatch 具有稳定的身份。我将此解释为当一个组件从 useReducer 中提取 dispatch 时,它不会在连接到该 dispatch 的状态发生变化时重新渲染,因为对 dispatch 的引用将始终相同。基本上,调度可以“像静态值一样对待”。

然而,当此代码运行时,该行dispatch(actionCreators.setLoadingOn())会触发对全局状态的更新,并且useLoading钩子会再次运行,因此dispatch(actionCreators.setLoadingOn()) (无限重新渲染 -_-)

我没有正确理解 useReducer 吗?或者我正在做的其他事情可能会导致无限重新渲染?

2个回答

第一个问题是你永远不应该在渲染时触发任何 React 状态更新,包括useReducers'sdispatch()useState's setter。

第二个问题是,调度 while 总是会导致 React 将状态更新排队并尝试调用 reducer,如果 reducer 返回一个新值,React 将继续重新渲染。无论您从哪个组件分派 - 导致状态更新和重新渲染是useReducer首要问题。

“稳定标识”意味着该dispatch变量将在渲染中指向相同的函数引用。

除了您在渲染时设置状态这一事实之外,我想我可以阐明如何利用 dispatch 的稳定身份来避免像您期望的那样不必要的重新渲染。

您的 Provider 值是一个对象 (value={{ isLoading, dispatch}})。这意味着当上下文的状态改变时(例如,当 isLoading 改变时),值本身的标识也会改变。因此,即使您有一个使用 dispatch的组件,如下所示:

const { dispatch } = useLoading()

当 isLoading 更改时,组件将重新渲染。

如果您觉得重新渲染失控,那么利用调度稳定标识的方法是创建两个提供者,一个用于状态(在本例中为 isLoading),另一个用于调度,如果你这样做,一个只需要像这样调度的组件:

const dispatch = useLoadingDispatch()

不会isLoading发生变化时重新呈现。

请注意,这可能是过度优化,在简单的场景中可能不值得。

这是一组关于该主题的进一步阅读的优秀文章:https : //kentcdodds.com/blog/how-to-optimize-your-context-value https://kentcdodds.com/blog/how-to-use - 有效地react上下文