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

时间:2020-01-31 22:38:18

标签: javascript reactjs redux react-hooks use-reducer

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

// 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(actionCreators.setLoadingOn())触发对全局状态的更新,并且useLoading钩子再次运行,dispatch(actionCreators.setLoadingOn()) (无限重新渲染- _-)

我不正确理解useReducer吗?还是我正在做的其他事情可能导致无限次重新渲染?

2 个答案:

答案 0 :(得分:5)

第一个问题是,渲染时切勿触发任何React状态更新,包括useReducers的{​​{1}}和dispatch()的设置器。

第二个问题是,是的,在调度的同时总是使React排队状态更新并尝试调用化简器,如果化简器返回新值,React将继续重新渲染。不管您从哪个组件调度的-引起状态更新和重新渲染都是useState的重点。

“稳定的身份”表示useReducer变量将跨渲染器指向相同的函数引用。

答案 1 :(得分:0)

除了已经指出的要在渲染时设置状态这一事实之外,我认为我可以阐明一些如何利用调度的稳定身份来避免像您期望的那样不必要的重新渲染的情况。

您的提供者值是一个对象(值= {{isLoading,dispatch}})。这意味着当上下文的状态更改时(例如,当isLoading更改时),值本身的标识也将更改。因此,即使您有一个仅 消耗调度的组件,如下所示:

const { dispatch } = useLoading()

isLoading更改时,组件将重新呈现。

如果您觉得重新渲染已不合时宜,则利用调度稳定身份的方法是创建两个Provider,一个用于状态(本例中为isLoading),另一个用于状态分派(如果这样做),则只需要这样分派的组件:

const dispatch = useLoadingDispatch()

isLoading发生更改时,不会会重新呈现。

请注意,这可能是过度优化,在简单的情况下可能不值得。

这是一组很好的文章,可以进一步阅读该主题: https://kentcdodds.com/blog/how-to-optimize-your-context-value https://kentcdodds.com/blog/how-to-use-react-context-effectively