Галоўная / Артыкулы

Чаму React.memo продовжае перераскладваць: ідэнтычнасць кансаю у прапасах

Пазіце, як React.memo супароўнае пропсы, чаму вбудованыя функціі, об’екты і масівы парадуюць яму, як стабілізаваць рэферэнсы, і чаму профілюванне павінна выконвацца раней, чым мемаўванне.

Выдавец: REACTAPP.TOP5 хв чытання1092 слоў

Крыніцы: React.memo: Useful, but Often Misused

React.memo частаюць спрыяць як прыем для паспрабоў падняць выдатнасць: яго насталяюць навакол компонента, і перыядрування зникае. У практыцы аплікацыі, заполненыя элементамі memo, можа ўсё ж застацца медленнымі, адтолькі як іншыя прычыны медленной роботы React-інтэрфейса — структура компонентаў, нестабільныя параметры, даскладныя вычысленні аб проста занадта большая колькасць перыядруванняў — не стосуюцца відсутнасці такога абгорткі. У гэтым кярыранцы паказана, з чым на самай справе порівняўчы працюе memo, якія патэрны параметраў таямна паслабляюць яго эфект, і як выявіць, чыму ўзагалі варта яго выкарыстоўваць.

Што на самай справе пераглядае memo

memo не заважае компаненту адрадзівацься. Ён паравярае новыя пропы з паказванымі раней, проп за пропам, выкарыстоўваючы Object.is, і праходзіць праз адрадзіванне толькі тады, калі ўсе яны аднаковыя. Змены ў сэнтэ развеў самога компаненту або ў кантэксте, які ён чытае, таксама спрычынаюць його адрадзіванне. Прімітывныя пропы, такія як строкі, цалы і булевы значэння, паравяраюцца за ўмярою, таму яны працуюць сама сабой.

import { memo } from "react";

const App = () => {
  const name = "John";
  const age = 25;

  return <UserCard name={name} age={age} />
};

const UserCard = memo(({ name, age }) => {
  return (
    <div>
      <h3>{name}</h3>
      <p>Age: {age}</p>
    </div>
  );
});

Якщо name і age не зменіліся, React можа перыскарыстаць паказваны раней выхід. Проблемы пачынаюцца з функцый, об’ектаў і масоў, таму што для яных Object.is паравярае рэферэнсы, а не ўместа.

Інлайн-калбэкі ствараюцца занова ў кожны раз пад час адрадзівання

Ёсць прыклад, калі родны элемент перадае функцыю-стрэлку кнопкі, якая ўраджана з memo.

import React, { useState, memo } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>Count: {count}</h2>

      <CounterButton
        onIncrement={() => setCount((prev) => prev + 1)}
      />
    </div>
  );
};

const CounterButton = memo(({ onIncrement }) => {
  return (
    <button onClick={onIncrement}>
      Increment
    </button>
  );
});

Кожная перрадарбація App створае новую функцыю. Яе тэлы ідэнтычны, але рэферэнс разны, таму memo бачыць змененыя праблемы і ўсё час перрадарбацуе CounterButton.

Рашэння — зберагчы стабільны рэферэнс за дапамою useCallback. Паколькі викорыстоўваецца форма апдэйта prev => prev + 1, масавык залежнасцей можа застацца пустым.

import React, { useState, useCallback } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const handleIncrement = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <div>
      <h2>Count: {count}</h2>

      <CounterButton
        onIncrement={handleIncrement}
      />
    </div>
  );
};

const CounterButton = ({ onIncrement }) => {
  return (
    <button onClick={onIncrement}>
      Increment
    </button>
  );
};

Заўважыце, што ў гэтым фрагменте CounterButton больш не абгорнуты ў memo. Без гэтага абгорнення дзеціны элементы перрадарбацуюцца разам з своім адзецем, і useCallback нічога не даёжы. Гэтыя два элементы ўзаемна корыстны толькі тады, калі memo(...) застаецца наваколо дзецінага элемента з пярэднега прыкладу. І не трэба дадаваць useCallback всюды; у яго є свае затраты, і ён падходзіць там, дзе рашае конкрэтную проблему.

Об’екты і масівы знаходзяцца ў той жа спосаб

Літерал об’екта, які знаходзится ў тэле компонента, ствараецца занова ў кожны раз, калі компонент атрыбутуецца.

import { memo } from "react";

const App = () => {

  const config = {
    theme: "dark",
    pageSize: 20,
    showFilters: true,
  };

  return (
    <div>
      <DataGrid config={config} />
    </div>
  );
};

const DataGrid = memo(({ config }) => {
  return (
    <div>
      <h3>Theme: {config.theme}</h3>
      <p>Page Size: {config.pageSize}</p>
    </div>
  );
});

То ж самае стосуецца і масівы параметраў.

import { memo } from "react";

const App = () => {
  const options = [
    { label: "Admin", value: "admin" },
    { label: "Editor", value: "editor" },
    { label: "User", value: "user" },
  ];

  return (
    <div>
      <Select options={options} />
    </div>
  );
};

const Select = memo(({ options }) => {

  return (
    <select>
      {options.map((option) => (
        <option key={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  );
});

Оба варыянты ствараюць новыя рэферэнсы ў кожны раз, таму ўсе іх дзецкі элементы атрыбутуецца занова.

Пераместіце даны, якія ніколі не зменяюцца

Калі даны ў статычным стане, пераместіце іх у масштаб модуля, ўпэўнівшыся, што яны будуць ствараны толькі раз. Спіс параметраў можа знаходзіцца за межами компонента.

import { useState, useMemo, memo } from "react";

const options = [
      { label: role.toUpperCase(), value: role },
      { label: "Editor", value: "editor" },
      { label: "User", value: "user" },
    ]

const App = () => {

  return (
    <div>
      <Select options={options} />
    </div>
  );
};

const Select = memo(({ options }) => {
  return (
    <select>
      {options.map((option) => (
        <option key={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  );
});

export default App;

Як практычна прымітка, першы элемент адносіцца да зменнай role, якой не існуе ў масштабе модуля, таму гэты код выклікае ReferenceError. Ўжыце літэрал, напрыклад { label: "Admin", value: "admin" }. Якщо элемент дасправды залежыць ад пропсаў чы стану, залучыце масів унутрь компонента і загорніце яго ў useMemo, указавшы гэтыя значэння як залежнасці (вось, мабыць, чаму імпортуецца useMemo).

Об’ект налаштавання таксама падлягае той самай обробцы.

import { memo } from "react";

const config = {
  theme: "dark",
  pageSize: 20,
  showFilters: true,
};

const App = () => {
  return (
    <div>
      <DataGrid config={config} />
    </div>
  );
};

const DataGrid = memo(({ config }) => {

  return (
    <div>
      <h3>Theme: {config.theme}</h3>
      <p>Page Size: {config.pageSize}</p>
    </div>
  );
});

Звярніце увагу пры вычысленні раней, чым застосаваць memoization

Прыткія запытанні выходзяць перад будаваннемэ: які элемент на самай працоўны, чаму ён атрымлівае візуальны выгляд, чы рэндераванне гэтага элемента є дорогім, і чы можна зменшыць колькасць виконваных задач у замест на ўсуненне іх. React DevTools Profiler адпавядае на большасць з іх, паказваючы, якія элементы былі атрымлены, чаму, сколькі часу кожны з іх викорыстаў і якія апдэйты былі непатрэбныя. Спіс задач у выканання меншай колькасці задач перад мемоізацыяй дапамагае разабраць гэты порядак дзействаў.

Ключовыя выводы

  • memo дапамагае толькі тады, калі кожны проп заставаецца стабільным.
  • Функцыі, об’екты, масівы і значэння контексту, якія знаходзяцца ўнутрь элемента, часта ускладняюць яго прыменэнне.
  • useCallback без мемоізаванага элемента-дзеця є марнай працай.
  • Застаўляйте useMemo для дорогіх вычысленняў або рэферэнсаў, якія павінны заставацца стабільнымі.
  • Віртуалізацыя дужо дугіх спісоў і разбіванне вялікіх складовых зазвычай дае большыя выгоды, чым мемаізацыя.
  • Спачатку створыце прафіль; нехай даныя самыя выберуць.