Предотвращение повторного рендеринга дочернего элемента, если родительский рендеринг выполняется повторно с помощью хуков

Мои данные bestSellerDummy не меняются, поэтому я хотел бы предотвратить повторную визуализацию одного и того же дочернего элемента Product при повторной визуализации родителя. Я пробовал использовать useMemo в родительском и React.memo в дочернем, но не повезло, он по-прежнему показывает журнал "Компонент рендеринга продукта..." каждый раз, когда родительский рендеринг повторяется. Что мне здесь не хватает? Пожалуйста посоветуй.

Примечание. Ожидается, что родительский элемент будет повторно отображаться каждый раз, когда я вызываю функцию addToCart (из CartContext) в компоненте Product.

Я использую CartContext, может быть связано с этим, я не уверен. Вот песочница: https://codesandbox.io/s/dazzling-moore-po1c6?file=/src/App.js

Home.tsx

const [bestSellerDummy] = useState(
  [...new Array(5)].map((item, key) => ({
    id: key,
    imageUri:'https://1.jpg',
    name: 'My Dummy 1',
    price: 25,
  })),
);

const bestSellers = useMemo(() => {
  return bestSellerDummy.map((productDummy, key) => {
    return (
      <Product key={key} product={productDummy} />
    );
  });
}, [bestSellerDummy]);

return (
  ...
  {bestSellers}
  ...
)

Product.tsx

const Product: FunctionComponent<IProductProps> = (
  productProps,
) => {
  ...
  console.log('Rendering Product component..');
  ...
}

export default React.memo(Product);

=== РЕДАКТИРОВАТЬ: МОЯ ВЕРСИЯ ОТВЕТА ===

В заключение! После игры с useCallback, useMemo, fast-memoize плагин.. Что мне больше всего подходит, так это использование useReducer в контексте сочетается с обертыванием дорогостоящего компонента с React.memo. Я считаю, что это наиболее чистый и элегантный способ оптимизации дочерних компонентов. Рабочая песочница находится здесь: https://codesandbox.io/s/eloquent-albattani-8x7h9?file=/src/App.js

5 ответов

Решение

Поскольку вы используете useContext, ваш компонент всегда будет повторно отрисовываться.

Когда ближайший над компонентом обновляется, этот Hook инициирует повторную визуализацию с последним значением контекста, переданным этому провайдеру MyContext. Даже если предок использует React.memo или shouldComponentUpdate, повторная визуализация все равно будет происходить, начиная с самого компонента с использованием useContext.

Ссылка: https://reactjs.org/docs/hooks-reference.html#usecontext

Я пытался реорганизовать ваш код, используя вторую стратегию, указанную в документации: https://github.com/facebook/react/issues/15156#issuecomment-474590693.

Однако вскоре я понял, что addToCart функция имеет cartItems как его зависимость, поэтому всякий раз, когда cartItems изменения, addToCart меняется, и избежать повторного рендеринга невозможно, так как каждый Product использование компонентов addToCart функция.

Это подводит меня к использованию useReducerпотому что React гарантирует, что его dispatch работает стабильно и не меняется при повторных рендерах.

Итак, вот рабочий Codesandbox: https://codesandbox.io/s/red-feather-dc7x6?file=/src/App.js:786-797

Заворачивать BestSellers компонент с React.memoтоже. Не использовать useMemoчтобы избежать ненужного обновления компонентов, так как это может вызвать ошибки. Он используется для вычисления дорогих значений.

Источник: https://reactjs.org/docs/hooks-reference.html#usememo

Это лучший способ прояснить ваши представления о useCallback, useMemo и useEffect.

App.js

      import Child1 from "./Child1";
import Child2 from "./Child2";
import { useState, useEffect, useMemo, useCallback } from "react";
function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  console.log("Parent");
  const printx = useCallback(() => {
    console.log("x:" + x);
  }, [x]);
  useEffect(() => {
    printx();
    console.log("-------");
  }, [printx]);
  const child1 = useMemo(() => {
    return <Child1 x={x} />;
  }, [x]);
  const child2 = useMemo(() => {
    return <Child2 y={y} />;
  }, [y]);
  return (
    <div className="App">
      <h1>Parent</h1>
      <button onClick={() => setX(x + 1)}>X+</button>
      <button onClick={() => setY(y + 1)}>Y+</button>
      {child1}
      {child2}
    </div>
  );
}

export default App;

Ребенок1.js

      const Child1 = ({ x }) => {
  console.log("Child1");
  return (
    <div>
      <h1>Child 1:{x}</h1>
    </div>
  );
};
export default Child1;

Ребенок2.js

      const Child2 = ({ y }) => {
  console.log("Child2");
  return (
    <div>
      <h1>Child 2:{y}</h1>
    </div>
  );
};
export default Child2;

Дело в том, что вы используете динамический индекс для ключа. когда вы использовали динамический ключ, всегда реагируйте на повторную визуализацию этого. Поэтому используйте идентификатор продукта или какой-либо уникальный ключ для этого, тогда проблема будет решена. у меня тоже такая проблема и я ее решил

Попробуй так

const [bestSellerDummy, setBestSellerDummy] = useState([]); // default empty

// get data from `useCallback`
const sellerData = React.useCallback(
  () => {

    return [...new Array(5)].map((item, key) => ({
     id: key,
     imageUri:'https://1.jpg',
     name: 'My Dummy 1',
     price: 25,
    }))

  }, []
);

useEffect( () => {
  setBestSellerDummy( sellerData() ); // set data when screen rendered from `useCallback`
}, [])

const bestSellers = useMemo(() => {
  ....
}, [bestSellerDummy]);

return (
  ...
  {bestSellers}
  ...
)
Другие вопросы по тегам