Чаму React.memo продовжае перераскладваць: ідэнтычнасць кансаю у прапасах
Пазіце, як React.memo супароўнае пропсы, чаму вбудованыя функціі, об’екты і масівы парадуюць яму, як стабілізаваць рэферэнсы, і чаму профілюванне павінна выконвацца раней, чым мемаўванне.
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для дорогіх вычысленняў або рэферэнсаў, якія павінны заставацца стабільнымі.