ShouldComponentUpdate в функциональных компонентах

У меня есть вопрос относительно React shouldComponentUpdate (если не перезаписан). Я предпочитаю чистые, функциональные компоненты, но я боюсь, что он обновляется каждый раз, даже если реквизит/состояние не изменилось. Поэтому я рассматриваю возможность использования класса PureComponent.

Мой вопрос относительно этого: Имеют ли функциональные компоненты те же shouldComponentUpdate как PureComponents? Или он обновляется каждый раз?

Ответ 1

В React функциональные компоненты не имеют состояния и не имеют методов жизненного цикла. Компоненты без сохранения состояния - это элегантный способ написания компонентов React без большого количества кода в нашем комплекте. Но внутренне компоненты без состояния помещаются в класс без каких-либо оптимизаций, применяемых в настоящее время. Это означает, что компоненты без сохранения состояния и с состоянием имеют один и тот же путь кода внутри (хотя мы определяем их по-разному).

Но в будущем React может оптимизировать компоненты без состояния, как сказано здесь:

В будущем также можно будет оптимизировать производительность для этих компонентов, избегая ненужных проверок и выделения памяти. [Подробнее...]

shouldComponentUpdate

Здесь вы можете применить наши собственные оптимизации и избежать ненужного повторного рендеринга компонентов. Использование этого метода с различными типами компонентов объясняется ниже:

  • Функциональные компоненты без состояния

    Как было сказано ранее, компоненты без сохранения состояния не имеют методов жизненного цикла, поэтому мы не можем оптимизировать их с помощью shouldComponentUpdate. Но они уже оптимизированы по-другому, имеют гораздо более простую и элегантную структуру кода и стоят меньше байтов, чем компонент со всеми хуками жизненного цикла.

  • расширить React.PureComponent

    Начиная с React v15.3.0, у нас есть новый базовый класс под названием PureComponent можно расширять с PureRenderMixin встроенного PureRenderMixin. Под капотом используется неглубокое сравнение текущих реквизитов/состояний со следующими реквизитами/состоянием в shouldComponentUpdate.

    Тем не менее, мы все еще не можем полагаться на класс PureComponent для оптимизации наших компонентов до желаемого уровня. Этот случай аномалии происходит, если у нас есть реквизиты с типами Object (массивы, даты, простые объекты). Это потому, что у нас есть эта проблема при сравнении объектов:

    const obj1 = { id: 1 };
    const obj2 = { id: 1 };
    console.log(obj1 === obj2); // prints false
    

    Следовательно, поверхностного сравнения недостаточно, чтобы определить, изменились ли вещи или нет. Но используйте класс PureComponent если ваши реквизиты - это просто строка, число, логическое значение.., а не объекты. Также используйте его, если вы не хотите реализовывать свои собственные оптимизации.

  • расширить React.Component

    Рассмотрим приведенный выше пример; если мы знаем, что объекты изменились, если изменился id, то мы можем реализовать нашу собственную оптимизацию путем сравнения obj1.id === obj2.id Именно здесь мы можем extend наш обычный базовый класс Component и использовать shouldComponentUpdate чтобы shouldComponentUpdate сравнивать конкретные ключи.

Ответ 2

Функциональный компонент будет перерисовываться каждый раз, когда родитель отображает его, независимо от того, изменились реквизиты или нет.

Однако, используя React.memo высокого порядка React.memo функциональные компоненты могут получить такую же проверку shouldComponentUpdate которая используется в PureComponent https://reactjs.org/docs/react-api.html#reactmemo.

Вы можете просто обернуть свой функциональный компонент в React.memo при экспорте, как показано здесь.

Так

const SomeComponent = (props) => (<div>HI</div>)
export default SomeComponent

Может быть вместо

const SomeComponent = (props) => (<div>HI</div>)
export default React.memo(SomeComponent)

пример

В следующем примере показано, как это влияет на рендеринг

Родительский компонент - это просто обычный функциональный компонент. Он использует новые обработчики реакции для обработки некоторых обновлений состояния.

У него просто есть некоторое состояние tick которое служит лишь для того, чтобы дать некоторую подсказку о том, как часто мы переопределяем реквизит, в то время как оно вызывает повторную визуализацию родительского компонента дважды в секунду.

Далее у нас есть состояние clicks которое говорит о том, как часто мы нажимали кнопку. Это то, что мы посылаем детям. Следовательно, они должны ТОЛЬКО пересматривать, если количество кликов изменяется, если мы используем React.memo

Теперь обратите внимание, что у нас есть два разных вида детей. Один завернут в memo а другой нет. Child который не обернут, будет перерисовываться каждый раз, когда перерисовывается родитель. MemoChild который упакован, будет перерисовываться только при изменении свойства кликов.

const Parent = ( props ) => {
  // Ticks is just some state we update 2 times every second to force a parent rerender
  const [ticks, setTicks] = React.useState(0);
  setTimeout(() => setTicks(ticks + 1), 500);
  // The ref allow us to pass down the updated tick without changing the prop (and forcing a rerender)
  const tickRef = React.useRef();
  tickRef.current = ticks;

  // This is the prop children are interested in
  const [clicks, setClicks] = React.useState(0);

  return (
    <div>
      <h2>Parent Rendered at tick {tickRef.current} with clicks {clicks}.</h2>
      <button 
        onClick={() => setClicks(clicks + 1)}>
        Add extra click
      </button>
      <Child tickRef={tickRef} clicks={clicks}/>
      <MemoChild tickRef={tickRef} clicks={clicks}/>
    </div>
  );
};

const Child = ({ tickRef, clicks }) => (
  <p>Child Rendered at tick {tickRef.current} with clicks {clicks}.</p>
);

const MemoChild = React.memo(Child);

Вы можете проверить пример здесь https://codepen.io/anon/pen/ywJxzV

Ответ 3

Другой подход заключается в использовании useMemo для обновления значений только при обновлении наблюдаемых значений:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

В случае объектов есть возможность использовать хук состояния для кэширования значения интересующей переменной после того, как она будет обновлена. Например, используя lodash:

const [fooCached, setFooCached]: any = useState(null);

if (!_.isEqual(fooCached, foo)) {
  setFooCached(foo);
}'). 

const showFoo = useMemo(() => {
    return <div>Foo name: { foo.name }</div>
}, [fooCached]);