Написание более быстрого кода React, часть 1: введение в memoize-one
введение
Различные типы услуг требуют различных стандартов производительности. Очевидно, что неразумно и ненужно требовать максимальной скорости и SEO для системы управления фоном ToB.
Первое, что нужно учитывать, это не как оптимизировать, а стоит ли оптимизировать, производительность React достаточно хороша, ведь «преждевременная оптимизация — это дьявол», ситуация всегда «хорошо, но не нужно».
Как разработчик, чрезвычайно важно иметь глубокое понимание того, где инструмент не работает, и иметь возможность его оптимизировать.
Оптимизацию производительности React можно условно разделить на два пункта:
- Уменьшить количество повторных рендеров (неизменяемые данные, shouldComponentUpdate, PureComponent)
- Уменьшить сложность повторного рендеринга (memoize-one)
В этой статье оптимизируется метод рендеринга на основе memoize-one, чтобы уменьшить ненужную сложность рендеринга.
существующие проблемы
Давайте сначала посмотрим на простой компонент, как показано ниже:
class Example extends Component {
state = {
filterText: ""
};
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
const filteredList = this.props.list.filter(item =>
item.text.includes(this.state.filterText)
);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</Fragment>
);
}
}
Этот компонент получает список, переданный родительским компонентом, отфильтровывает filteredList, содержащий filterText, и отображает его.
в чем проблема?
В отсутствие какой-либо обработки рендеринг родительского компонента всегда будет вызывать рендеринг дочернего компонента, даже если состояние/реквизиты дочернего компонента не изменились, если количество отфильтрованных данных велико, а логика фильтрации сложна, это будет очень важным моментом оптимизации.
Для достижения какого эффекта?
- Когда state(filterText)/props(list) не изменились, рендеринг не будет выполняться (введение-оптимизация производительности, пункт 1), который пока не будет обсуждаться здесь.
- Когда состояние (filterText)/реквизиты (список) не изменилось, выполните рендеринг и повторно используйте последний результат вычисления.
memoize-one
A memoization library which only remembers the latest invocation
основное использование
import memoize from "memoize-one";
const add = (a, b) => a + b; // 基本计算方法
const memoizedAdd = memoize(add); // 生成可缓存的计算方法
memoizedAdd(1, 2); // 3
memoizedAdd(1, 2); // 3
// Add 函数没有被执行:上一次的结果直接返回
memoizedAdd(2, 3); // 5
// Add 函数被调用获取新的结果
memoizedAdd(2, 3); // 5
// Add 函数没有被执行:上一次的结果直接返回
memoizedAdd(1, 2); // 3
// Add 函数被调用获取新的结果
// 即使该结果在之前已经缓存过了
// 但它并不是最近一次的缓存结果,所以缓存结果丢失了
Поняв основы использования, давайте оптимизируем приведенный выше случай.
Случай оптимизации
import memoize from "memoize-one";
class Example extends Component {
state = { filterText: "" };
// 只有在list或filterText改变的时候才会重新调用真正的filter方法(memoize入参)
filter = memoize((list, filterText) =>
list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// 在上一次render后,如果参数没有发生改变,`memoize-one`会重复使用上一次的返回结果
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</Fragment>
);
}
}
Анализ исходного кода
Менее 20 строк, если это связано с ТС, а комментарии удалены. memoize-one по сути является функцией высшего порядка. Она фактически вычисляет функцию как параметр и возвращает новую функцию. Новая функция будет кэшировать последний входной параметр и последнее возвращаемое значение. Если входной параметр равен последнему входному параметру параметра, затем верните последнее возвращаемое значение, в противном случае снова вызовите функцию реального расчета и кэшируйте входные параметры и результаты для следующего использования.
Представьте, что здесь есть блок-схема :)
// 默认比较先后入参是否相等的方法,使用者可自定义比较方法
import areInputsEqual from './are-inputs-equal';
// 函数签名
export default function<ResultFn: (...any[]) => mixed>(
resultFn: ResultFn,
isEqual?: EqualityFn = areInputsEqual,
): ResultFn {
// 上一次的this
let lastThis: mixed;
// 上一次的参数
let lastArgs: mixed[] = [];
// 上一次的返回值
let lastResult: mixed;
// 是否已经初次调用过了
let calledOnce: boolean = false;
// 被返回的函数
const result = function(...newArgs: mixed[]) {
// 如果参数或this没有发生变化或非初次调用
if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
// 直接返回上一次的计算结果
return lastResult;
}
// 参数发生变化或者是初次调用
lastResult = resultFn.apply(this, newArgs);
calledOnce = true;
// 保存当前参数
lastThis = this;
// 保存当前结果
lastArgs = newArgs;
// 返回当前结果
return lastResult;
};
// 返回新的函数
return (result: any);
}
В качестве альтернативы вы можете использоватьdeckoЭта библиотека со встроенными декораторами bind/memoize/debounce хорошо совместима с React.
Расширение: последовательность Фибоначчи
Ниже приведен пример вычисления последовательности Фибоначчи с использованием итерации вместо рекурсии и использования замыкания для кэширования предыдущих результатов.
const createFab = () => {
const cache = [0, 1, 1];
return n => {
if (typeof cache[n] !== "undefined") {
return cache[n];
}
for (let i = 3; i <= n; i++) {
if (typeof cache[i] !== "undefined") continue;
cache[i] = cache[i - 1] + cache[i - 2];
}
return cache[n];
};
};
const fab = createFab();
Суммировать
В этой статье рассказывается об использовании и принципе библиотеки memoize-one, основанной на React, и достигается эффект, аналогичный вычисляемому свойству Vue (computed) в React, основанном на кэшировании результатов вычислений для уменьшения ненужной сложности рендеринга.
С точки зрения развития бизнеса API, предоставляемый Vue, значительно повышает эффективность разработки.
React не решает многих проблем сам по себе, но благодаря активному сообществу можно найти решения проблем, возникающих в работе, и, изучая эти решения, мы можем изучить многие классические идеи программирования, тем самым уменьшив зависимость от фреймворков.
Я всегда говорил, что React сделает вас лучшим разработчиком JavaScript», — Тайлер МакГиннис.
Ссылка на ссылку
- You Probably Don't Need Derived State, от React Team
- Технология Memoize, Леон
- memoize-one, автор: AlexReardon
- Вычисляемые свойства и слушатели, от Вью