[Перевод] Memo в React
Много статей написано об оптимизации производительности React. В основном, если где-то обновление state происходит медленно, то вам нужно:
Убедиться, что у вас запущен сборка под production. (Сборка dev умышленно медленнее, в крайнем случае — даже на порядок)
Убедиться, что вы не подняли state выше по дереву, чем это необходимо. (Например, размещение state в централизованном хранилище может быть не лучшей идеей)
Запустите React DevTools Profiler, чтобы увидеть, что будет заново отрисовано, оберните самые дорогие [по ресурсам] суб-деревья с помощью memo. (При необходимости добавьте
useMemo
)
Этот последний шаг, он раздражает, особенно для промежуточных компонентов, в идеале — компилятор сделает это за вас. В будущем. Возможно.
В этом посте, я хочу поделиться 2 другими подходами. Они так просты, что люди редко понимают, что это улучшает производительность рендера.
Эти подходы дополняют то, что вы уже знаете! Они не заменяют memo
или useMemo
, но будет хорошее идеей начать с них.
Медленный компонент (искусственно)
Вот компонент с серьёзной проблемой производительности:
import { useState } from 'react';
export default function App() {
let [color, setColor] = useState('red');
return (
setColor(e.target.value)} />
Привет, мир!
);
}
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// искусственная задержка -- ничего не делаем 100мс
}
return Я - очень медленное дерево компонентов.
;
}
(Попробуйте здесь)
Проблема в том, что каждый раз, когда цвет меняется внутри App
, мы повторно рендерим
, в которой мы искусственно добавили задержку, чтобы сделать его медленным.
Я мог бы поместить его в memo()
и остановиться на этом, но уже есть много статей, поэтому я не буду тратить на это время. Я хочу показать 2 других решения.
Решение 1: перенесите state вниз
Если вы внимательно посмотрите на код рендеринга, то заметите, что только часть возвращенного дерева компонентов на самом деле заботится о цвете:
export default function App() {
let [color, setColor] = useState('red');
return (
setColor(e.target.value)} />
Привет, мир!
);
}
Так что давайте извлечём эту часть в компонент Form
и спустим state в этот компонент:
export default function App() {
return (
<>
>
);
}
function Form() {
let [color, setColor] = useState('red');
return (
<>
setColor(e.target.value)} />
Hello, world!
>
);
}
(Попробуйте здесь)
Теперь, если цвет изменится, то заново рендерится только Form
. Задача решена.
Решение 2: поднимите контент вверх
Вышеупомянутой решение не работает, если часть state используется где-то над дорогим деревом. Для примера, давайте предположим, что мы поместили цвет в родительский Hello, world! (Попробуйте здесь) Теперь кажется, что мы не можем просто извлечь нужные части, которые не используют цвет, в другой компонент, поскольку он будет включать родительский Или все же сможем? Поиграйте в этой песочнице и посмотрите, сможете ли вы разобраться. Ответ на удивление прост Ответ Hello, world! (Попробуйте здесь) Мы разделили компонент Части, которые не работают с цветом, остались в компоненте Когда цвет меняется, В результате Прежде, чем применять оптимизацию, такую как Эти подходы интересны тем, что сами по себе они не имеют ничего общего с производительностью. Использование children prop для разделения компонентов обычно упрощает отслеживание потока данных в вашем приложении и сокращает количество prop, которые идут вниз. Повышение производительности в таких случаях — это как вишенка на торте, а не конечная цель. Любопытно, что данный паттерн также откроет больше преимуществ в будущем. Например, когда серверные компоненты готовы к внедрению, наш компонент ColorPicker может получать свои children с сервера. На сервере может работать либо весь компонент Этого не сможет сделать даже Затем, если этого не хватило, то используйте Profiler и добавьте эти Да, возможно Это не новая идея. Это естественная реакция на композиционную модель React. Это достаточно просто, но эти подходы недооценивают, а они заслуживают немного больше любви.export default function App() {
let [color, setColor] = useState('red');
return (
. Теперь без memo
не обойтись, не так ли?
export default function App() {
return (
App
на 2 части. Части, зависящие от цвета, вместе с самим цветом мы по поместили в ColorPicker
. App
и передаются в ColorPicker
как JSX-контент (children prop).ColorPicker
выполняет повторный рендер. Но у него всё ещё есть children prop, который получен из App
в прошлый раз, поэтому React не заходит в это суб-дерево.
не рендерится заново.Какова же мораль?
memo
или useMemo
, имеет смысл посмотреть, а может вы сможете отделить части, которые меняются, от частей, которые не меняются.
, либо его части, и даже изменение state верхнего уровня React «пропускает» на клиент.memo
! Но опять же, оба подхода дополняют друг друга. Не забывайте перемещать состояние вниз (а контент вверх).memo
.Я мог об этом раньше прочитать где-то ещё?