Обычно после завершения разработки бизнес-функций мне интересно, будете ли вы продолжать совершенствоваться и заниматься оптимизацией производительности? Во фреймворке React есть некоторые соображения, связанные с оптимизацией производительности, и если вы не уделяете им особого внимания, их можно легко проигнорировать. В следующей статье будет представлен соответствующий опыт нескольких оптимизаций производительности, которые будут использованы в работе.
Key
При отображении данных структуры списка используйтеkey
Можно сказать, что это стало лучшей практикой в разработке React. тогда вы знаете, почему мы используемkey
? Причина в том, чтобы использоватьkey
компонентподдерживать структурную устойчивость. Все мы знаем, что React известен своим алгоритмом DOM Diff, который уникален в процессе фактического сравнения обновлений узлов.key
Это позволяет React быстрее находить измененные узлы, чтобы минимизировать количество обновлений.
В процессе фактического использования многие люди часто используют непосредственно индекс массива для удобства.key
, что очень опасно. Поскольку данные массива часто добавляются или удаляются, значение нижнего индекса может стать нестабильным. Поэтому в процессе разработки следует максимально избегать такой ситуации.
В следующем примере компонент списка продуктов демонстрирует использование ключей:
class ShopMenu extends React.Component {
render() {
return (
<ul>
{
this.props.shopItems.map((shopItem) => <ShopItem key={shopItem.id} itemName={shopItem.name}></ShopItem>)
}
</ul>
)
}
}
Сравнение данных
React как превосходный фреймворк для интерфейса сам по себе многое для нас сделал. Однако в процессе разработки, если мы сможемПредотвращение ненужного повторного рендеринга компонентов, может улучшить работу разработанных компонентов.
Поверхностное сравнение
В процессе обновления компонента важен процесс сравнения данных, и он является ключом к запуску повторного рендеринга компонента. Поэтому нам необходимо глубоко понимать механизм изменения данных компонентов React в процессе обновления. Метод сравнения React по умолчанию для обновлений состояния — поверхностное сравнение, мы можем взглянуть на егореализация исходного кода:
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
Кроме того, для равного сравнения объектовis
метод, отличный от непосредственного использования===
или==
, это для спец.+0
а также-0
,NaN
а такжеNaN
Выравнивание фиксировано, неявное преобразование не выполняется. Его реализация такова:
/**
* inlined Object.is polyfill to avoid requiring consumers ship their own
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
*/
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
Как видно из приведенного выше кода, для эталонных объектов алгоритм поверхностного сравнения сначала будет использоватьObject.keys
Получите все атрибуты объекта и сравните соответствующие значения атрибутов. Однако здесь сравниваются только данные первого слоя, а рекурсивное сравнение не производится. Вероятно, поэтому это и называется «поверхностным сравнением».
shouldComponentUpdate
Для компонента Class мы можем использоватьshouldComponentUpdate
метод, чтобы определить, следует ли выполнять компонентный рендеринг, чтобы улучшить производительность страницы. Этот метод будет выполняться каждый раз, когда свойства и состояние меняются.Реализация фреймворка по умолчанию для этого метода заключается в том, чтобы возвращать true напрямую, то есть каждый раз, когда свойства и состояние меняются, компонент будет перерисовываться. И если нам ясна логика изменения данных, мы можем полностью реализовать процесс сравнения вручную, чтобы избежать повторного рендеринга:
class ShopItem extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.itemName !== nextProps.itemName;
}
render() {
return (<div>{this.props.itemName}</div>);
}
}
pureComponent
Для достижения цели оптимизации производительности иногда нет необходимости реализовывать вручнуюshouldComponentUpdate
. Все, что вам нужно сделать, это сделать ваш компонент унаследованным от React.PureComponent, он уже имеет встроенный алгоритм поверхностного сравнения, поэтому приведенный выше пример можно переписать так:
class ShopItem extends React.PureComponent {
render() {
return (<div>{this.props.itemName}</div>);
}
}
О стрелочных функциях
Еще одна вещь, которую следует помнить, — это быть осторожным при использовании стрелочных функций:
class Button extends React.Component {
render() {
return <button onClick={() => {console.log('hello, scq000');}}>click</button>
}
}
Привязать стрелочную функцию непосредственно к компоненту легко написать, но это приведет к потере производительности, поскольку функция будет регенерироваться каждый раз при ее рендеринге. Использование встроенных стрелочных функций вызовет повторный рендеринг, даже если никакие другие реквизиты или состояние компонента не изменились.
Итак, чтобы избежать этой ситуации, мы можем сначала объявить функцию прослушивателя событий, а затем получить ее ссылку и передать компоненту:
class Button extends React.Component {
handleClick = () => {
console.log('hello, scq000');
}
render() {
return <button onClick={this.handleClick}>click</button>
}
}
useCallback
Если мы используем функциональные компоненты, в React16useCallback
Крюк дает нам новую идею:
export const Button = (text, alertMsg) => {
const handleClick = useCallback(() => {
// do something with alertMsg
}, [alertMsg]);
return (
<button onClick={handleClick}>{text}</button>
);
}
Передайте функцию стрелки вuseCallback
метод, который является функцией более высокого порядка, возвращающей запомненный метод. Этот метод обновляется только тогда, когда свойства или состояние зависят от изменений. В приведенном выше примере, когда его зависимое состояниеalertMsg
при смене,handleClick
функция будет обновлена.
В React16 вы также можете использоватьuseEffect
Этот крючок для обработки некоторых побочных эффектов, как это:
const Student = ({name, age}) => {
useEffect(() => {
doSomethingWithInfos(infos)
}, [name, age]);
return (
<div>This is a child component.</div>
);
}
const Person = () => {
return (<Student name="scq000" age="11" />)
}
ДатьuseEffect
Второй переданный параметр также является его зависимостью, если в этой зависимости используется стрелочная функция, то каждый разuseEffect
Функция обратного вызова будет выполнена. Таким образом, результат может быть не таким, как мы хотим.useCallback
чтобы этого не произошло.
useCallback
Хотя кэширование функций возможно, в большинстве сценариев его использование увеличивает сбор мусора и увеличивает время выполнения функции-оболочки. Только для функций с большим объемом вычислений используйтеuseCallback
для достижения хорошего эффекта оптимизации.
useMemo
Помимо непосредственно функций кэширования, иногда необходимо кэшировать данные и результаты вычислений. Ключом к реализации мемоизации является запоминание последнего значения состояния и выходного значения. Мы можем реализовать упрощенный метод Memorize, используя замыкания:
function memorize(func) {
let lastInput = null;
let lastOuput = null;
return function() {
// 这里使用浅比较来判断参数是否一致
if (!shallowEqual(lastInput, arguments)) {
lastOuput = func.apply(null, arguments);
}
lastInput = arguments;
return lastOuput;
}
}
В РеакцииuseMemo
hook был для нас, чтобы реализовать эту функциональность непосредственно на нем:
const calcResult = React.useMemo(() => expensiveCalulate(a, b), [a, b]);
Когда входные параметры a и b не изменились, последнее значение будет использоваться автоматически. Это также означает, что мы используемuseMemo
Может использоваться только для кэширования результатов чистых функций. Для операций с большим объемом вычислений можно эффективно избежать повторного процесса вычислений.
React.Memo
Для функциональных компонентов из-за отсутствияshouldComponentUpdate
метод, рассмотрите возможность использованияReact.Memo
Чтобы оптимизировать производительность компонента: React.Memo — это компонент более высокого порядка со встроеннымuseMemo
метод для кэширования всего компонента.
Рассмотрим следующий код:
function Demo() {
return (
<Parent props={props}>
<Child title={title} subtitle={subtitle} />
</Parent>
);
}
Родительский компонент перерисовывается из-за изменения свойств в свойствах.Даже если реквизиты дочернего компонента не меняются, дочерний компонент Child также будет перерисовываться. В этом случае рассмотрите возможность использованияReact.Memo
Чтобы кэшировать дочерние компоненты:
export function Card({title, subtitle}) {
// do some render logic
}
export const MemoziedCard = React.Memo(Card);
Чтобы глубже понять эту часть логики, давайте рассмотрим соответствующиеисходный код:
if (updateExpirationTime < renderExpirationTime) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
Мы видим, что React.Memo по умолчанию также использует неглубокий алгоритм сравнения, поэтому для сложных данных нам нужно самим реализовать логику сравнения данных. допустимыйReact.Memo
Передайте второй параметр следующим образом:
const compartor = (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
}
React.Memo(Card, compartor)
Неизменяемые данные Неизменяемые
ImmutableЭто абстрактная структура данных, инкапсулированная Facebook. Благодаря неизменности и совместному использованию ее структуры, она может ускорить сравнение ссылочных объектов. Данные, созданные с помощью Immutable, являются неизменяемыми, поэтому их легко отследить в приложении. Это также соответствует идее функционального программирования. Его ядром является использование постоянной структуры данных.Когда данные изменяются, будет обновляться только измененная часть, а неизмененная часть структуры данных будет использовать одну и ту же ссылку для достижения цели совместного использования структуры. Поэтому при глубоком копировании сильно вложенных данных производительность будет выше.
import Immutable from 'immutable';
var obj = Immutable.fromJS({1: "one"});
var map = Immutable.Map({a: 1, b: 2, c: 3});
map.set('b', 4);
var list = Immutable.List.of(1,2,3);
list.push(5);
Хотя Immutable JS имеет свои преимущества в производительности, помните о влиянии использования. Не смешивайте нативные объекты с неизменяемыми объектами, это приведет к снижению производительности, поскольку производительность преобразования неизменяемых данных в нативные объекты JS очень низкая. Для получения рекомендаций по использованию Immutable JS вы можете обратиться кэта статья.
reselect
В процессе использования Redux данные о состоянии компонента обычно выводятся из состояния, и необходимо выполнить много логики вычислений. Предположим, теперь дерево состояний в моем приложении выглядит так:
const state = {
a: {
b: {
c: 'c',
d: 'd'
}
}
};
каждый разa.b.c
При обновлении, даже если d не обновляется, все ссылки наa.b.d
места также будут пересчитаны.
Затем на этом этапе мы хотим оптимизировать использование кеша или мемоизации. Для этой цели родился reselect, он может помочь нам избежать повторных вычислений:
import {createSelector} from "reselect";
const shopItemSelector = (state) => state.shopItems;
const parentSelector = (state) => state.parent;
export const shopMenuSelector = createSelector(
[shopItemSelector, parentSelector],
(shopItems, parent) => {
// do something with shopItems and parent
}
);
только статусshopItems
а такжеparent
После изменения он будет пересчитан.
По умолчанию сравнение старых и новых атрибутов также выполняется с использованием мелких сравнений. В сочетании с неизменным введенным в предыдущем разделе мы можем дополнительно оптимизировать процесс выравнивания.
Первый — переключить все наше дерево состояний на неизменяемую структуру данных:
const state = Immutable.fromJS(originState);
Затем при переопределении производного состояния используйтеImmutable
серединаis
Сравнивать:
import {createSelectorCreator, defaultMemoize} from 'reselect';
import { is } from 'immutable';
const createImmutableSelector = createSelector(defaultMemoize, is);
export const shopMenuSelector = createImmutableSelector(
[shopItemSelector, parentSelector],
(shopItems, parent) => {
// do something with shopItems and parent
}
);
нагрузка по требованию
Методы оптимизации, представленные выше, в основном основаны на механизме рендеринга компонентов, а методы, которые будут представлены далее, основаны наРасчет задержкиИдеи по оптимизации отклика приложений. Хотя это не позволяет сократить общее время рендеринга, оно может ускорить взаимодействие пользователя со страницей, тем самым улучшив взаимодействие с пользователем в приложении.
До React 16 мы обычно хотели реализовать ленивую загрузку, используяreact-loadable
и т. д., но теперь можно использовать напрямуюReact.lazy
метод подойдет. По сути, он также использует разделение кода для задержки загрузки некоторых неосновных компонентов. нужно использоватьReact.lazy
тоже надо сотрудничатьSuspense
компоненты вместе.Suspense
Компоненты могут обеспечивать базовые эффекты перехода для лениво загруженных компонентов, обычно предоставляяloading
Анимация:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
Однако в настоящее время эта стратегия поддерживается только на стороне браузера. Для приложений React, использующих SSR, рассмотрите https://github.com/smooth-code/loadable-components для той же цели.
Тест производительности
Известный гуру менеджмента Питер Друкер однажды сказал: «Если вы не можете что-то измерить, вы не можете это улучшить». Хотя это предложение относится к менеджменту, оно применимо и к разработке программного обеспечения. Прежде чем рассматривать оптимизацию производительности страниц React, мы должны выполнить соответствующую работу по тестированию, чтобы найти узкое место в производительности. Производительность рендеринга компонентов можно проверить с помощью React DevTools Profiler, который можно найти по адресугугл магазинСкачать в. [Ошибка загрузки изображения... (image-e0f95d-1564019981226)] Более конкретное использование см.реагировать JS.org/blog/2018/0….
Суммировать
Оптимизация производительности всегда является больной точкой и трудностью в разработке программного обеспечения.Много знаний нужно изучать и практиковать.Можно только сказать, что предстоит пройти долгий путь. Однако преждевременная оптимизация не рекомендуется на работе. Несмотря на то, что производительность является важным критерием, в процессе разработки необходимо также найти компромиссы с точки зрения удобства сопровождения кода и будущей адаптации. Не все части приложения должны быть молниеносными, и ремонтопригодность некоторых частей часто важнее.
Если оптимизация производительности должна быть выполнена, ядром является сокращение частых вычислений и рендеринга.Существует три основных способа реализации стратегий:Используйте ключ для поддержания стабильности структуры компонентов, оптимизации процесса сравнения данных и загрузки по требованию.. Среди них оптимизированный процесс сравнения данных может быть выполнен с использованием кэшированных данных или компонентов или с использованием неизменяемых неизменяемых данных и т. д. в соответствии с конкретными сценариями использования. Наконец, мы также должны помнить об использовании инструментов тестирования для сравнения производительности до и после, чтобы убедиться в эффективности работы по оптимизации.
Справочная статья
вооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо
Кент CD odds.com/blog/use MEM…
реагировать JS.org/docs/op kickmeter…
реагировать JS.org/blog/2018/0…
Горячее цинкование. Это .org/recipes/u…
--Пожалуйста, укажите источник---
Наконец, приглашаю всех обратить внимание на мой официальный аккаунт и вместе учиться и общаться.