【问题标题】:How to stop re-rendering when using useContext (Reactjs)?使用useContext(Reactjs)时如何停止重新渲染?
【发布时间】:2020-06-15 05:46:14
【问题描述】:

我有一个简单的反应应用程序,其中有一个 FruitsList 组件用于显示列表中的水果,一个 FruitForm 组件用于添加水果,两者都包含Fruits 组件内。我正在使用useContextuseReducer 来管理水果的状态。我已经创建了一个 FruitContext 。我想停止 FruitForm 的重新渲染,因为它仅使用调度功能并且每次添加新水果时重新渲染它都是无用的。请提出任何相同的解决方案。

表单组件

const Form = () => {

  const { dispatch } = useContext(FruitsContext);
  const { setLoading } = useContext(LoaderContext);

  let formRef = null;
  const fruit = {};

  const formSubmitHandler = async (event) => {
      event.preventDefault();
      setLoading(true);
      await fetch('https://fruit-basket-74269.firebaseio.com/fruits.json', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(fruit)
        });
        dispatch({type: 'ADD', fruit: fruit});
        // formRef.reset();
        setLoading(false);
      }

      return (
        <Card>
          {console.log('[Form]')}
          <form ref={ref => formRef = ref} onSubmit={formSubmitHandler} className={style.form} autoComplete="off">
            <div className={style.formGroup}>
              <input onChange={event => fruit.item = event.target.value} className={style.input} type="text" id="name" placeholder="Enter fruit name" />
              <label className={style.label} htmlFor="name">Name</label>
            </div>
            <div className={style.formGroup}>
              <input onChange={event => fruit.qty = event.target.value} className={style.input} type="number" min="0" id="qty" placeholder="Enter quantity" />
              <label className={style.label} htmlFor="qty">Quantity</label>
            </div>
            <Button>Add Fruit</Button>
          </form>
        </Card>
      )
}

export default React.memo(Form);

水果列表

const FruitList = () => {

  const { fruits } = useContext(FruitsContext);
  console.log('[FruitList]:', fruits);

  return useMemo(() => {

    return (
      <div className={style.fruitList}>
        <h2 className={style.heading}>Fruits</h2>
        <hr />
        <div className={style.list}>
          <FruitCard name={'Apple'} qty={15} />
          <FruitCard name={'Orange'} qty={10} />
          <FruitCard name={'Grapes'} qty={20} />
        </div>
      </div>
    );
  }, []);
}

export default FruitList;

水果

const Fruits = () => {

  console.log('[Fruits Parent]');

  // const { loading } = useContext(LoaderContext);

  return (
    <div className={style.fruits}>
      {/* {loading && <Loader />} */}
      <Form />
      <br />
      <Filter />
      <br />
      <FruitList />
    </div>
  )
}

export default Fruits

FruitContext

export const FruitsContext = createContext();

const FruitsProvider = ({children}) => {
  const [fruits, dispatch] = useReducer(reducer, []);

  const value = ({
    fruits, dispatch
  });

  return (
    <FruitsContext.Provider value={value}>
      { children }
    </FruitsContext.Provider>
  );
}

export default FruitsProvider;

水果还原剂

export default (state, action) => {
  switch(action.type) {
    case 'LOAD':
      return action.fruits
    case 'ADD':
      console.log('[Pre-Action]', state);
      const newList = [...state];
      newList.push(action.fruit);
      console.log('[Post-Action]', newList);
      return newList;
    case 'DELETE':
      return state.filter(fruit => fruit.id !== action.id);
    default: return state;
  }
}

【问题讨论】:

  • 请显示一个最小的工作代码
  • 请分享您的代码
  • 你用的是什么表单插件?如果 formik 尝试 FastField:jaredpalmer.com/formik/docs/api/fastfield
  • 我已经添加了相同的代码
  • 不,我没有使用任何表单插件

标签: reactjs react-hooks use-reducer use-context


【解决方案1】:

如果提供者值中的任何内容发生更改,使用上下文的组件将始终重新呈现。无论您是否实际使用该值(例如,在这种情况下,即使您只拉出调度函数)。

通常,对于大多数 React 应用程序,您不需要优化类似的东西,React 已经非常快,并且一些额外的重新渲染不会造成伤害。任何性能问题都可以在发生的时间和地点得到解决。 如果你想从一开始就进行优化,你可以拆分你的 reducers 状态并分派到两个不同的上下文中。它们都可以放在同一个 ProviderComponent 中,但必须有两个不同的 Context.Provider 组件。一种将状态用作值,另一种将调度函数用作值。 如果您随后使用调度上下文,如果调度操作更改状态,它不会导致组件重新呈现。

// 更新

举个例子:

const FruitsProvider = ({children}) => {
  const [fruits, dispatch] = useReducer(reducer, []);

  return (
    <FruitsStateContext.Provider value={fruits}>
      <FruitsDispatchContext.Provider value={dispatch}>
        { children }
      </FruitsDispatchContext.Provider>
    </FruitsStateContext.Provider>
  );
}

我还建议不要直接导出上下文,而是导出暴露状态或调度的钩子。

例如

export const useFruits = () => {
  const fruitsState = React.useContext(FruitsStateContext);
  if (!fruitsState) {
    throw new Error('you cant use the useFruits hook outside the FruitsStateContext');
  }
  return fruitsState;
}

【讨论】:

  • 感谢它的工作,但不是拆分上下文,我们不能只使用 React.memo 或 useMemo 来停止重新渲染
  • 当然,您也可以这样做,但根据我的经验,使用 useMemo 经常会导致意外错误,因为有时您可能会错过一些依赖项。使用 React.memo 通常是最好的选择,但在这里可能无济于事,因为它仍然会重新渲染,因为它基本上是状态更改。但是您可以阻止组件进一步向下重新渲染。根据受影响的组件数量,我仍然会尝试拆分提供程序。仍然:最重要的是 - 如果您没有遇到任何性能问题,我认为无论如何都不需要进行任何优化。
猜你喜欢
  • 2018-07-20
  • 1970-01-01
  • 2016-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-26
  • 1970-01-01
  • 2017-11-15
相关资源
最近更新 更多