使用 useContext (Reactjs) 时如何停止重新渲染?

How to stop re-rendering when using useContext (Reactjs)?

我有一个简单的 React 应用程序,其中有一个 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;

FruitReducer

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;
  }
}

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

通常你不需要为大多数反应应用程序优化类似的东西,反应已经相当快并且一些额外的重新渲染不会受到伤害。任何性能问题都可以在发生的时间和地点得到解决。 如果您想从一开始就进行优化,您可以将 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;
}