При написании кода React Hook,useCallback
а такжеuseMemo
Часто сбивает с толку. Хотя мы знаем, что их функции заключаются в кэшировании и оптимизации производительности, нас беспокоит негативная оптимизация из-за неправильного использования. Эта статья объяснитuseCallback
а такжеuseMemo
Общие методы использования и недоразумения в разработке, в сочетании с анализом исходного кода причин, знать, что и почему.
1.useCallback
1.1 Не злоупотреблятьuseCallback
Рассмотрим следующие примеры:
import React from 'react';
function Comp() {
const onClick = () => {
console.log('打印');
}
return <div onClick={onClick}>Comp组件</div>
}
когдаComp
Когда компонент сам запускает обновление или в качестве дочернего компонента следует за родительским компонентом для обновления, мы замечаемonClick
будет переназначен. Чтобы «улучшить производительность», используйтеuseCallback
пакетonClick
Для целей кэширования:
import React, { useCallback } from 'react';
function Comp() {
const onClick = useCallback(() => {
console.log('打印');
}, []);
return <div onClick={onClick}>Comp组件</div>
}
Итак, вопрос в том, улучшилась ли производительность? Ответ — нет, не так хорошо, как раньше, после того, как мы перепишем логическую структуру кода, причина будет предельно ясна:
import React, { useCallback } from 'react';
function Comp() {
const onClick = () => {
console.log('打印');
};
const memoOnClick = useCallback(onClick, []);
return <div onClick={memoOnClick}>Comp组件</div>
}
Выполнение каждой дополнительной строки кода влечет за собой затраты, даже если эта стоимость представляет собой всего лишь небольшое количество тепла процессора. В официальной документации указано, чтоНе нужно беспокоиться о создании функций, вызывающих проблемы с производительностью., поэтому используйтеuseCallback
Преобразование компонентов в этом сценарии не дает никаких преимуществ (функция все равно будет создана), но стоимость, которую она приносит, обременяет компоненты (нужно сравнивать, изменились ли зависимости),useCallback
Чем больше вы используете, тем больший вес вы несете. С точки зрения javascript, когда компонент обновляется, он неuseCallback
Обернутый метод будет удален сборщиком мусора и переопределен, ноuseCallback
Сделанное закрытие будет содержать ссылку на функцию обратного вызова и зависимости.
1.2 useCallback
правильный способ использования
Причина ошибкиuseCallback
Первоначальная цель дизайна не в том, чтобы решить проблему многократного создания внутренних функций компонентов, а в том, чтобы уменьшить ненужную повторную отрисовку подкомпонентов. На самом деле в системе React есть две основные идеи оптимизации:
- 1. Уменьшите количество повторных рендеров. Поскольку наиболее ресурсоемкой частью React является согласование, согласование не будет запущено, пока оно не отобразится.
- 2. Уменьшить объем расчета, об этом естественно говорить не приходится.
Итак, рассмотрим следующие сценарии:
import React, { useState } from 'react';
function Comp() {
const [dataA, setDataA] = useState(0);
const [dataB, setDataB] = useState(0);
const onClickA = () => {
setDataA(o => o + 1);
};
const onClickB = () => {
setDataB(o => o + 1);
}
return <div>
<Cheap onClick={onClickA}>组件Cheap:{dataA}</div>
<Expensive onClick={onClickB}>组件Expensive:{dataB}</Expensive>
</div>
}
Expensive
является очень дорогим компонентом для рендеринга, но щелчокCheap
компоненты также могут вызыватьExpensive
перерисовать, даже еслиdataB
Ничего не изменилось. Причина в том,onClickB
переопределяется, в результате чего React определяет, что компонент изменился при сравнении старого и нового компонентов. В этот моментuseCabllback
а такжеmemo
Это сработало:
import React, { useState, memo, useCallback } from 'react';
function Expensive({ onClick, name }) {
console.log('Expensive渲染');
return <div onClick={onClick}>{name}</div>
}
const MemoExpensive = memo(Expensive);
function Cheap({ onClick, name }) {
console.log('cheap渲染');
return <div onClick={onClick}>{name}</div>
}
export default function Comp() {
const [dataA, setDataA] = useState(0);
const [dataB, setDataB] = useState(0);
const onClickA = () => {
setDataA(o => o + 1);
};
const onClickB = useCallback(() => {
setDataB(o => o + 1);
}, []);
return <div>
<Cheap onClick={onClickA} name={`组件Cheap:${dataA}`}/>
<MemoExpensive onClick={onClickB} name={`组件Expensive:${dataB}`} />
</div>
}
memo
Это новый метод, добавленный React v16.6.0.Подобно PureComponent, первый отвечает за оптимизацию функционального компонента, а второй — за компонент класса. Оба они выполняют поверхностное сравнение старых и новых данных, переданных в компонент, и, если они совпадают, они не запускают рендеринг.
такuseCallback
обещатьonClickB
Без изменений, нажмите сейчасCheap
Компонент не срабатываетExpensive
Обновление компонента, только нажмитеExpensive
компонент сработает. Внедряя оптимизации для уменьшения ненужного рендеринга,useCallback
а такжеmemo
Это пара оружия.Запустите пример кода
1.3 Расширение
useCallback
Исходный код выглядит следующим образом:
// 初始化阶段
function mountCallback(callback, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
// 更新阶段
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook();,
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
// 比较是否相等
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 如果相等,返回旧的 callback
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
Основная логика заключается в сравненииdeps
Есть ли изменение, если есть изменение, верните новыйcallback
функция, в противном случае вернуть исходную функцию. из них сравнительные методыareHookInputsEqual
Внутренние вызовы Reactis
Метод инструмента:
// 排除以下两种特殊情况:
// +0 === -0 // true
// NaN === NaN // false
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
);
}
2.useMemo
2.1 Не злоупотреблятьuseMemo
import React, { useMemo } from 'react';
function Comp() {
const v = 0;
const memoV = useMemo(() => v, []);
return <div>{memoV}</div>;
}
СоздайтеmemoV
Накладные расходы не нужны по тем же причинам, что и в разделе 1. Это необходимо только в том случае, если само поведение создания влечет за собой большие накладные расходы (например, тысячи вычислений для создания значения переменной).useMemo
, конечно, такие сцены редкость.
2.2 useMemo
правильный способ использования
Ранее мы упоминали, что одна из двух основных идей по оптимизации производительности компонентов React заключается в уменьшении объема вычислений, что такжеuseMemo
Использование:
import React, { useMemo } from 'react';
function Comp({ a, b }) {
const v = 0;
const calculate = (a, b) => {
// ... complex calculation
return c;
}
const memoV = useMemo((a, b) => v, [a, b]);
return <div>{memoV}</div>;
}
3. Резюме
React Hook предъявляет очень высокие требования к командному сотрудничеству и согласованности.useCallback
а такжеuseMemo
Эта пара методов является хорошим примером, для более сложных сценариев есть также парыuseRef
, использование пользовательских хуков и многое другое. Исходя из опыта, командам необходимо усилить проверку кода при кодировании ловушек, в противном случае могут возникнуть трудно обнаруживаемые ошибки или проблемы с производительностью. В настоящее время различные методы Hook не совершенны, и в Twitter есть много аргументов Мы с нетерпением ждем последующих версий React, которые предоставят более зрелые и простые в использовании решения.