Оптимизация производительности React (1) Когда PureComponent встречается с ImmutableJS

внешний интерфейс JavaScript React.js Immutable.js
Оптимизация производительности React (1) Когда PureComponent встречается с ImmutableJS

1. Болевые точки

По нашему впечатлению,ReactКажется, это означает компонентизацию и высокую производительность.Нам всегда нужно заботиться только о данных в целом, а разница между двумя даннымиUIКак изменить, то полностьюReact Virtual DomизDiff 算法сделать это. Так что мы произвольно манипулируем данными и в основном оптимизируемshouldComponentUpdateМне лень писать, ведь можно и без записи правильно отрендерить. Однако по мере того, как размер приложения становится все больше и больше, вы обнаружите, что страница работает немного медленнее, особенно когда компоненты вложены больше, а структура данных более сложна, изменение элемента формы или выполнение фильтра на список будет потреблять много энергии.100msВыше, на этот раз нам нужно оптимизировать! Конечно, если вы не сталкиваетесь с узкими местами в производительности, не беспокойтесь, преждевременная оптимизация — это зло. Здесь мы суммируем очень простую схему, чтобы сделатьReactПроизводительность приложения максимальна. В следующих разделах мы сначала рассмотрим некоторые базовые знания, в том числе:JavaScriptтип переменной иReactМеханизм рендеринга, если вы старый пташка, можете его сразу пропустить.

2. Обзор некоторых базовых знаний

1. Тип переменной

В JavaScript есть два типа переменных:

  • Основные типы: 6 основных типов данных,Undefined,Null,Boolean,Number,String,Symbol
  • Тип ссылки: совместно именуемыйObjectТип, подразделяется на:ObjectТипы,ArrayТипы,DateТипы,RegExpТипы,Functionтип и т.д.

Например:

let p1 = { name: 'neo' };
let p2 = p1;
p2.name = 'dave';
console.log(p1.name); // dave

В ссылочном типе объявитеp1объект, поставитьp1назначить наp2, в это время фактически присваивается адрес объекта в куче, а не данные в куче, то есть две переменные указывают на одно и то же место хранения, и следующиеp2.nameПосле изменения это также влияетp1. Хотя это может сэкономить память, когда приложение сложное, вам нужно быть очень осторожным с данными, потому что, если вы не уделите внимание изменению значения одной переменной, это может повлиять на другую переменную. Если мы хотим, чтобы они не влияли друг на друга, нам нужно скопировать одни и те же данные.Копия делится на поверхностную копию и глубокую копию.Поверхностная копия будет копировать только данные первого слоя, а глубокая копия будет рекурсивно копировать все слои копирует, что потребляет больше производительности.

2. React

существуетReactв, каждый разsetState,Virtual DOMвычислит два виртуальныхDOMРазница между объектами, а затем изменение реального необходимо изменитьDOM. из-заjsРасчет быстрый, а операция реальнаяDOMотносительно медленный,Virtual DOMИзбегайте ненужной правдыDOMоперация, такReactПроизводительность хорошая. Однако по мере увеличения сложности приложенияDOMДеревья становятся все более сложными, и большое количество операций сравнения также может влиять на производительность. напримерTableкомпонента, измените одну из строкTrполе компонента,setStateпосле всех остальных строкTrкомпоненты также будут выполняться один разrenderфункция, которая на самом деле не нужна. мы можем пройтиshouldComponentUpdateФункция решает, обновлять ли компонент. Большую часть времени мы можем знать, какие компоненты не изменятся, нет необходимости вычислять, какая часть виртуальногоDOM.

3. Чистый компонент

React15.3Добавлен новый класс вPureComponent, ранееPureRenderMixin,а такжеComponentВ принципе то же самое, но вrenderПрежде чем помочь компоненту автоматически выполниться один разshallowEqual(поверхностное сравнение), чтобы решить, следует ли обновлять компонент, поверхностное сравнение похоже на поверхностное копирование, сравнивается только первый слой. использоватьPureComponentЭто равносильно пропуску записиshouldComponentUpdateфункция, когда компонент обновляется, если компонентpropsа такжеstate:

  1. Ни эталон, ни данные первого уровня не изменились,renderМетод не сработает, а это тот эффект, которого нам нужно добиться.
  2. Хотя первый слой данных не изменился, но ссылка изменилась, это приведет к виртуальномуDOMВычислительные отходы.
  3. Первый слой данных меняется, но ссылка не меняется, что не приведет к отрисовке, поэтому с данными нужно работать осторожно.

4. Неизменяемый.js

Immutable.jsдаFacebookсуществует2014Библиотека постоянных структур данных, созданных в течение года. Постоянство означает, что после создания данные не могут быть изменены. Любая операция модификации, добавления или удаления вернет новуюImmutableобъект. Это может облегчить нам решение таких проблем, как кэширование, откат, обнаружение изменения данных и т. д., и упростить разработку. и предоставляет большое количество подобных нативныхJSметод иLazy Operationфункции, полнофункциональное программирование.

import { Map } from "immutable";
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1 !== map2; // true
map1.get('b'); // 2
map2.get('b'); // 50
map1.get('a') === map2.get('a'); // true

можно увидеть, изменитьmap1недвижимость возвращаетсяmap2, они не указывают на одно и то же место хранения,map1Он объявлен только, и все операции его не изменят.

ImmutableJSПредоставляет большое количество методов для обновления, удаления и добавления данных, что значительно облегчает нам работу с данными. Кроме того, родные типы иImmutableJSМетоды определения типа и преобразования:

import { fromJS, isImmutable } from "immutable";
const obj = fromJS({
  a: 'test',
  b: [1, 2, 4]
}); // 支持混合类型
isImmutable(obj); // true
obj.size(); // 2
const obj1 = obj.toJS(); // 转换成原生 `js` 类型

ImmutableJSДве самые большие особенности:immutable data structures(постоянная структура данных) сstructural sharing(Совместное использование структуры), постоянная структура данных гарантирует, что данные не могут быть изменены после их создания.При использовании старых данных для создания новых данных старые данные не изменятся и не будут похожи на исходныеjsТаким образом, операции с новыми данными повлияют на старые данные. Структурное совместное использование означает, что данные, которые не изменились, имеют общую ссылку, что не только снижает потребление производительности при глубоком копировании, но и уменьшает объем памяти. Например, следующая картинка:

tree
tree

Слева — старое значение, справа — новое значение, мне нужно изменить значение красного узла слева, сгенерированное новое значение изменяет все узлы между красным узлом и корневым узлом, то есть значение из всех голубых узлов старое значение ничего не меняет, другие места, где оно используется, не будут затронуты, и более половины синих узлов по-прежнему используются совместно со старым значением. существуетImmutableJSВнутри создается специальная структура данных, объединяющая нативные значения с рядом частных свойств для созданияImmutableJSТип, каждый раз, когда значение изменяется, он сначала проходит вспомогательное обнаружение частного свойства, затем изменяет соответствующее частное свойство и реальное значение, которое необходимо изменить, и, наконец, генерирует новое значение. средний, поэтому производительность будет очень высокой.

5. Кейсы

Сначала мы рассмотрим использование толькоReactВ случае, почему производительность приложения тратится впустую, адрес кода:GitHub.com/Worryless/Share-отвратительно…, в этом случае используетсяcreate-react-app, инструмент обнаружения используетchromeПлагин:React Perf. воплощать в жизнь

git clone https://github.com/wulv/fe-example.git
cd fe-example/react-table
yarn
yarn start

Вы можете открыть страницу, начать запись, затем изменить столбец данных по желанию и закончить запись.Вы можете видеть, что мы изменили только одну строку данных, но вPrint WastedВ этом элементе рендерингTrКомпонент потрачен впустую 5 раз:

react-table
react-table

Будь то операция добавления или удаления, она будет потрачена впустую.n-1второсортныйrender,потому чтоAppвесь компонентstateИзменилось, все компоненты будут перевоспитаны один раз, и, наконец, сравнение должно быть реальнымDOMоперация. мы кладемTableкомпоненты иTrунаследовалComponentизменить наPureComponent,Так,TrКаждый раз, когда компонент обновляется, это будет сделано один разshallowEqualДля сравнения, после записи один раз вы обнаружите, что операция модификации не тратится впустую, но на этот раз операции добавления и удаления недействительны, а анализ добавленных операций:

 add = () => {
    const  { data } = this.state;
    data.push(dataGenerate())
    this.setState({
      data
    })
  }

data.pushне изменилсяdataцитаты, такPureComponentизshallowEqualвернулся сразуtrue, не ходиrender. Это не то, что нам нужно, поэтому, если мы используемComponentОпределенно приведет к потерям производительности, используйтеPureComponentТакже необходимо убедиться, что при необходимости обновления компонентовpropsилиstateВозвращает новую ссылку, иначе она не будет обновленаUI.

В настоящее время,ImmutableJSможет показать свою силу, потому что гарантирует, что каждая модификация возвращает новыйObject, давайте посмотрим на модифицированный пример: адрес кода:GitHub.com/Worryless/Share-отвратительно…, выполните ту же операцию, что и в приведенном выше примере, вы увидите:

react-immutablejs
react-immutablejs

Добавляйте, удаляйте, изменяйте операции, ни одной траты. Причина отсутствия отходов заключается в том, что все дочерние компоненты используютPureComponent,ImmutableJSОперация модификации гарантированно возвращает новую ссылку, и изменяются только те узлы, которые необходимо изменить (PureComponentмогут быть отображены новые изменения), ссылки на другие узлы остаются неизменными (PureComponentнапрямую без рендеринга). Как можно заметить,PureComponentа такжеImmutableJSЭто естественная пара, если их объединитьredux, было бы совершеннее. потому чтоreduxизreducerдолжен каждый раз возвращать новую ссылку, иногда нам приходится использоватьcloneилиassignи т. д., чтобы гарантировать возврат новой ссылки, используйтеImmutanleJSЕстественно гарантирует это, нет необходимости вообщеlodashДождитесь библиотеки функций, например, я используюredux + immutable + react-router + expressНаписал немного более сложный пример:GitHub.com/Worryless/Share-отвратительно… pageIndexизstoreСтатус:

{
  loading: false,
  tableData: [{
    "name": "gyu3w0oa5zggkanciclhm2t9",
    "age": 64,
    "height": 121,
    "width": 71,
    "hobby": {
      "movie": {
        "name": "zrah6zrvm9e512qt4typhkt9",
        "director": "t1c69z1vd4em1lh747dp9zfr"
      }
    }
  }],
  totle: 0
}

Если мне нужна быстрая модификацияwidthЗначение 90, сравните использование глубокого копирования,Object.assignа такжеImmutableJSРазница между тремя способами:

// payload = { name: 'gyu3w0oa5zggkanciclhm2t9', width: 90 }
// 1. 使用深拷贝
 updateWidth(state, payload) {
    const newState = deepClone(state);
    return newState.tableData.map(item => {
      if (tem.name === payload.name) {
        item.width = payload.width;
      }
      return item;
    });
  }
// 2. 使用Object.assign
 updateWidth(state, payload) {
    return Object.assign({}, state, {
      tableData: state.state.map(item => {
        if (item.name === payload.name) {
          return Object.assign({}, item, { width: payload.width });
        }
        return item;
      })
    })
  }
// 3. 使用ImmutableJS
 updateWidth(state, payload) {
  return state.update('tableData', list => list.update(
      list.findIndex((item) => item.get('name') === payload.name),
    item => item.set('width', payload.width)));
  }

Использование глубокой копии — дорогостоящая операция, и ссылки изменяются, что неизбежно приводит кre-render, а такжеObject.assignбудет поверхностно копировать первый слой, хотя это не вызоветre-render, а вот мелкая копия один раз копирует все остальные атрибуты, что тут тоже очень не нужно, только использоватьImmutableJSМодификации выполняются идеально с минимальным кодом.

6. Преимущества и недостатки

Как можно заметить,ImmutableJSкомбинироватьPureComponentприложение может быть значительно сокращеноre-renderКоличество раз может значительно улучшить производительность. Но есть еще некоторые недостатки:

  1. Чтобы получить свойства компонента, вы должны использоватьgetилиgetInоперации (кромеRecordтипа), такой и родной.Операция гораздо более хлопотная, и если компонент был написан ранее, он нуждается в значительной модификации.
  2. ImmutableJSОбъем библиотеки относительно большой, около 56к, открытыйgzip16k после сжатия.
  3. стоимость обучения.
  4. трудно отлаживать, вredux-loggerкоторый должен быть вstateTransformerВыполнить в конфигурацииstate.toJS().

7. Лучшие практики

На самом деле важно то, что программисты должны иметь представление об оптимизации производительности, знакомое сjsХарактеристики ссылочных типов, понимание природы вещей важнее, чем использование определенного фреймворка или библиотеки. Это также может быть достигнуто другими методамиImmutableJSэффектов, таких как добавление данных, можно использовать оператор деструктуризации:

 add = () => {
    const  { data } = this.state;
    this.setState({
      data: [...data, dataGenerate()]
    })
  }

Однако, если данные глубоко вложены, их запись еще более проблематична. Вот несколько советов:

  1. Есть также две облегченные библиотеки, реализующие неизменяемые структуры данных:seamless-immutableилиimmutability-helper, но принцип совсем другой, да и КПД не такой высокий.
  2. Избегайте интенсивного использованияtoJSоперация, которая приводит к потере производительности.
  3. не делай простымJavaScriptобъект сImmutable.JSсмешивание
  4. комбинироватьreduxкогда используешьimport { combineReducers } from 'redux-immutablejs';,потому чтоreduxизcombineReducersожидатьstateчистыйjsобъект.
  5. как можно большеstateРазработан, чтобы быть плоским.
  6. Компоненты дисплея не используютImmutableструктура данных.
  7. Уходитеrenderодин в функцииPureComponentкомпонентpropsиспользоватьbind(this)илиstyle={ { width: '100px' } },потому чтоshallowEqualСравнение точно не пройдет.

8. Справочные ссылки

Эта статья была впервые опубликована вБлог о технологиях Youzan.