Оптимизация функционального компонента React

React.js

предисловие

ReactПосле его запуска было три способа определения компонентов, а именно:

  • функциональные компоненты
  • React.createClassСоздание компонентов
  • React.ComponentСоздание компонентов

Я считаю, что наиболее часто используемыми в повседневной жизни являются функциональные компоненты иReact.ComponentКомпоненты, сегодня я кратко расскажу о двух методах оптимизации функциональных компонентов.

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

Что такое функциональный компонент

Прежде чем говорить о функциональных компонентах, давайте рассмотрим концепцию —纯函数.

Что такое чистая функция?

цитировать абзацВикипедияКонцепция чего-либо.

В программировании функцию можно рассматривать какчистая функция:

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

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

Можно видеть, что чистые функции с одинаковыми входными данными должны производить одинаковые выходные данные и не иметь побочных эффектов.

Точно так же выход функциональных компонентов также зависит только отpropsа такжеcontext,а такжеstateЭто не имеет значения.

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

  • нет жизненного цикла
  • нет экземпляра компонента, нетthis(Я полагаю, что многие студентыthisраздражающий)
  • Нет внутреннего состояния (state)

Функциональные преимущества в сборе

  • Декларация не требуетсяclass,нетconstructor,extendsЖдите код, код лаконичен, а памяти мало.
  • Не нужно использоватьthis
  • Может быть записана как чистая функция без побочных эффектов.
  • лучшая производительность. Функциональные компоненты не имеют жизненного цикла и не нуждаются в управлении этой частью, что обеспечивает лучшую производительность.

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

  • Методов жизненного цикла нет.
  • Не инстанцируется.
  • нетshouldComponentUpdate, не может избежать повторного рендеринга.

React.memo

один пример

Вот пример, код такой, так же можно нажатьздесьсмотреть.

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

function App() {
  const [n, setN] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  return (
    <div className="App">
      <div>React.memo demo-1</div>
      <button onClick={onClick}>update {n}</button>
      <Child />
    </div>
  );
}

const Child = () => {
  console.log("render child");
  return <div className="child">Child Component</div>;
};

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Что это дает? Это очень просто: родительский компонент является оберткой для дочернего компонента.

Давайте судить об этом, когда я нажимаю на родительский компонентbuttonвозобновитьN, в дочернем компонентеlogБудет ли он реализован?

Согласно общему мнению, какое отношение обновление вашего родительского компонента имеет к моему дочернему компоненту? моего дочернего компонентаDOMОбновлять не надо, раз обновления нет, во что еще играть?log.

Но фактический эффект заключается в том, что каждый щелчокbutton, дочерний компонентlogбудет выполнен один раз, хотя дочерний компонентDOMОн не обновляется, но это не означает, что функция рендеринга дочернего компонента не выполняется.

Ниже рендеринг казни.

React.memo demo-1.gif

оптимизация

Для вышеуказанной ситуацииclassкомпоненты могут использоватьshouldComponentUpdateоптимизировать, а как насчет функциональных компонентов?ReactОн также предоставляет метод оптимизации, т.React.memo.

memoкоторыйmemorized, что значит помнить. Если входные параметры не меняются, а по определению чистой функции, выход не изменится, поэтому нет необходимости напрямую возвращать ранее запомненный результат.

const Child = React.memo(() => {
  console.log("render child");
  return <div className="child">Child Component</div>;
});

БудуChildиспользоватьReact.memoОбработайте это так, чтобы независимо от того, сколько раз вы нажимаете на родительский компонентbutton, дочерний компонентlogне будет выполняться.

полная кодовая точказдесь.

Схема эффекта выглядит следующим образом:

React.memo demo-2.gif

React.useCallback

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

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

function App() {
  const [n, setN] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  return (
    <div className="App">
      <div>React.useCallback demo-1</div>
      <button onClick={onClick}>update {n}</button>
      <Child onClick={() => {}} />
    </div>
  );
}

const Child = React.memo((props: { onClick: () => void }) => {
  console.log("render child");
  return <div className="child">Child Component</div>;
});

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Глядя на код, видно, что изменений нет, но подкомпонент принимает пустую функцию.

Затем проблема возникает снова, на этот раз нажмитеbuttonподкомпонентlogБудет ли он реализован?

Когда вы видите это, вы должны думать, что вы каждый раз передаете пустую анонимную функцию.propsОн не изменился, поэтому нет необходимости перерисовывать его. Каков конкретный результат, посмотрим на эффект:

React.useCallback demo-1.gif

можно увидеть каждый кликbuttonКогда дочерний компонентlogвсе равно будет выполняться снова. Так почему это?

потому что каждый щелчокbuttonПри обновлении родительского компонента будет сгенерирована пустая анонимная функция.Хотя все они являются пустыми анонимными функциями, они не являются одной и той же функцией.

Функция — это значение сложного типа,JavaScriptПри сравнении значений сложных типов сравнивайте их адреса памяти. Если это не одна и та же функция, то и адрес памяти будет другим.Reactрассмотрит подкомпонентыpropsИзменено, дочерний компонент будет повторно отображаться.

оптимизация

Так как же убедиться, что дочерний компонент каждый раз принимает одну и ту же функцию?

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

код показывает, как показано ниже. также нажмитездесьПроверить.

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

const childOnClick = () => {};

function App() {
  const [n, setN] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  return (
    <div className="App">
      <div>React.useCallback demo-2</div>
      <button onClick={onClick}>update {n}</button>
      <Child onClick={childOnClick} />
    </div>
  );
}

const Child = React.memo((props: { onClick: () => void }) => {
  console.log("render child");
  return <div className="child">Child Component</div>;
});

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Схема эффекта выглядит следующим образом:

React.useCallback demo-2.gif

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

недостаток

Если функция дочернего компонента зависит от значения в родительском компоненте, то такой подход не сработает.

Как это сделать? Если функция также может бытьmemorizedДостаточно.

Hook

Reactсуществует16.8.0официально запущен в версииHooks, из которых одинHookназываетсяuseCallback, который преобразует функцию вmemorizedизменять.

useCallbackПринимает два параметра, первый параметр — функция, второй параметр — массив зависимостей, возвращаетmemorizedпосле функции. Он вернет новую функцию, только если значение в зависимом массиве изменилось.

посмотрите на использованиеuseCallbackПосле кода вы также можете нажатьздесьПроверить.

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const childOnClick = React.useCallback(() => {
    console.log(`m: ${m}`);
  }, [m]);
  return (
    <div className="App">
      <div>React.useCallback demo-3</div>
      <button
        onClick={() => {
          setN(n + 1);
        }}
      >
        update n: {n}
      </button>
      <button
        onClick={() => {
          setM(m + 1);
        }}
      >
        update m: {m}
      </button>
      <Child onClick={childOnClick} />
    </div>
  );
}

const Child = React.memo((props: { onClick: () => void }) => {
  console.log("render child");
  return (
    <div className="child">
      <div>Child Component</div>
      <button onClick={props.onClick}>log m</button>
    </div>
  );
});

const rootElement = document.getElementById("root");
render(<App />, rootElement);

В приведенном выше коде дочерний компонент принимаетuseCallbackфункция, ее зависимый параметр равенm, только тогда, когдаmКогда есть изменение, функция, принятая дочерним компонентом, будет регенерированной функцией. То есть независимо от того, сколько раз обновление будет нажатоnизbutton, дочерние компоненты не будут обновлены, только нажмите, чтобы обновитьmизbutton, дочерний компонент будет обновлен.

Посмотри, как это работает:

React.useCallback demo-3.gif

Фактический эффект соответствует нашим ожиданиям.

искривленный ум

Когда вы увидите это, некоторые студенты подумают, что если вы используетеuseCallbackПри передаче пустого массива в качестве зависимого массива родительский компонент больше не будет влиять на дочерний компонент, даже если ваш родительский компонентmИзменено, мои дочерние компоненты по-прежнему не будут повторно отображаться, разве это не лучшая производительность? Без лишних слов, давайте проверим это. код нажмитездесьПроверяем, эффект такой:

React.useCallback demo-4.gif

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

Из этого можно сделать вывод, что следствием передачи пустого массива в качестве зависимого является то, что параметры в функции, принимаемой подкомпонентом, всегда инициализируются для использования.useCallbackЦенность времени, такой результат не то, что мы хотим.

Так что кривого ума все же меньше.

Суммировать

вместе сReactофициально запущенHooks, приносит ряд новых функций, которые значительно расширяют функциональность функциональных компонентов, используя эти новые функции для достижения иclassкомпоненты имеют одинаковый эффект.

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

React HooksНеважно, ароматный ты или нет, в любом случае, я ароматный в первую очередь.

Скажи что-нибудь тихо,Vue 3.0Также представлены функциональные компоненты.