Тщательно изучите все тонкости оптимизации производительности React Native.

внешний интерфейс JavaScript React.js React Native
Тщательно изучите все тонкости оптимизации производительности React Native.

предисловие

В последнее время проект RN находится на реконструкции.Просматривая различные материалы, начиная с нижнего слоя RN, я подумал и обобщил некоторые вопросы, связанные с оптимизацией производительности, от react до react-native.

Performance · React Native
Сначала внимательно ознакомьтесь с этой главой официальной документации (документация на английском языке).
Пожалуйста, обратите внимание на высокую энергию впереди: Анбандлинг + инлайн требует этого раздела, китайских документов нет! ! !

Первый взгляд на распространенные причины проблем с производительностью
image

Здесь я сначала приведу свои собственные выводы, затем начну с основополагающих принципов, чтобы понять, почему я это делаю, и, наконец, конкретное развитие каждого метода (продолжение следует)
c6c076d9-2dae-4c56-b382-83b7f268814a

Эта часть не является мертвым знанием.Может быть, однажды у меня появятся более широкие идеи и решения, которые могут опровергнуть текущий вывод, поэтому эта статья будет продолжать обновляться. . .

Обзор оптимизации производительности RN

Прежде чем говорить о производительности, давайте сначала разберемся, как работает RN.

Через RN мы можем использовать JS для реализации кросс-платформенного приложения, как сказал FB: напиши один раз, работай везде.
74b378bb-b3f8-4faa-98a5-cb713ee86008

RN предоставляет нам среду выполнения JS, поэтому разработчикам внешнего интерфейса нужно заботиться только о том, как писать код JS, а для рисования пользовательского интерфейса нужно только рисовать в виртуальной DOM, и не нужно уделять особое внимание конкретным Платформа

Что касается грязной работы по преобразованию JS-кода в собственный код, то нижний уровень RN сделал всю работу.

bb6cd988-3da0-45ef-b362-d9818b903cc5

Суть RN в том, чтобы настроить бридж посередине, чтобы JS и натив могли звонить друг другу

Процесс загрузки РН в основном делится на несколько этапов

  • Инициализировать среду RN
    • Создать мост
    • JS-окружение в Bridge
    • Модули РН, компоненты пользовательского интерфейса
  • Скачать пакет JS
  • Запустить пакет JS
  • визуализировать страницу

Dive into React Native performance | Engineering Blog | Facebook Code | Facebook
ed25dbb5-e80d-4f83-a087-13291f2cd48d

Благодаря тесту производительности версии FaceBook для ios получен приведенный выше график времени.
Мы можем увидеть зеленыйJS Init + RequireЗанимают более половины времени, основная операция этой части — инициализация JS-окружения: загрузка JS Bundle, запуск JS Bundle

JS Bundle — это JS-файл, упакованный инструментами разработки RN, который содержит не только JS-код компонентов страницы RN, но также JS-код реакции и реакции-натива, а также редукции, реакции-навигации и т. д., которые мы часто use файл JS Bundle после минимизации очень простой демонстрационной страницы RN приближается к 700 КБ, поэтому размер файла JS Bundle является узким местом оптимизации производительности.

Предположим, у нас есть большое приложение, которое включает в себя множество страниц, но при обычном использовании многие страницы даже не будут открываться, есть какие-то сложные файлы конфигурации и редко используемые функции, эти связанные коды, в которых приложение не нужно. начинается, то мы можем рассмотреть возможность разделения для оптимизации производительности

Что касается того, как уменьшить размер пакета пакетов, текущий основной метод состоит в том, чтобы разделить пакет пакетов и разделить код фреймворка и бизнес-код. Код фреймворка очень большой, поэтому его нужно разделить и предварительно загрузить отдельно, в то время как бизнес код становится очень маленьким, код JS выпускается отдельно, а некоторые ссылки на предыдущий опыт приведены ниже.

Но перед распаковкой официальный представитель ФБ также упомянул несколько моментов оптимизации, которые следует сделать лучше перед этим

Doing less

  • Cleanup Require/Babel helpers
  • Avoid copying and decoding strings when loading the bundle
  • Stripping DEV-only modules

Scheduling

  • Lazy requires
  • Relay incremental cache read
  • De-batching bridge calls, batch Relay calls
  • Early UI flushing
  • Lazy native modules loading
  • Lazy touch bindings on text components

Создание обобщения React-Native и оптимизация производительности — веб-интерфейс Tencent IVWeb Team Community
Как и ожидалось от Tencent, в основном речь идет об общем строительстве, местных субподрядах пакетов и онлайн-анализе эффективности проектов.
Пакетная трансформация субподряда RN
RN упаковывает эти вещи | YMFE
Решение для распаковки и горячего обновления React Native · Solartisan

Говоря об отделении, официальная документация также анализирует встроенные требования вместе.

Встроенные требования откладывают запрос модуля или файла до тех пор, пока этот файл действительно не понадобится.

Легко понять, глядя на небольшой пример

import React, { Component } from 'react';
import { Text } from 'react-native';
// ... import some very expensive modules

// You may want to log at the file level to verify when this is happening
console.log('VeryExpensive component loaded');

export default class VeryExpensive extends Component {
  // lots and lots of code
  render() {
    return <Text>Very Expensive Component</Text>;
  }
}
import React, { Component } from 'react';
import { TouchableOpacity, View, Text } from 'react-native';

// 先把这个组件赋值为null
let VeryExpensive = null;

export default class Optimized extends Component {
  state = { needsExpensive: false };

  didPress = () => {
    if (VeryExpensive == null) {
		// 真正需要这个组件的时候才加载
      VeryExpensive = require('./VeryExpensive').default;
    }

    this.setState(() => ({
      needsExpensive: true,
    }));
  };

  render() {
    return (
      <View style={{ marginTop: 20 }}>
        <TouchableOpacity onPress={this.didPress}>
          <Text>Load</Text>
        </TouchableOpacity>
		  // 根据需要判断是否渲染该组件
        {this.state.needsExpensive ? <VeryExpensive /> : null}
      </View>
    );
  }
}

Even without unbundling inline requires can lead to startup time improvements, because the code within VeryExpensive.js will only execute once it is required for the first time

Вышеуказанное содержание в основном на первой оптимизации производительности скорости рендеринга экрана

Так где же точка производительности после входа в приложение? или вернуться к мосту

Во-первых, под аурой Apple и Google скорость работы нативного кода на устройстве не вызывает сомнений, а JS, как язык сценариев, известен своей быстротой, а это означает, что независимая работа обеих сторон происходит очень быстро. , так что кажется, что узкое место производительности появится только в связи между двумя концами, но две стороны на самом деле не общаются напрямую.Вместо этого они используют мост в качестве посредника для поиска, вызова модулей, интерфейсов и другой логики работы, которая сделает слой пользовательского интерфейса ясно воспринимаемым.Caton, то управление производительностью становится тем, как минимизировать логику, необходимую для моста.

  • Ответ на событие пользовательского интерфейса
    Вся эта часть контента происходит на нативной стороне и передается на сторону JS в виде событий Это всего лишь триггер и не будет иметь чрезмерных проблем с производительностью.
  • обновление пользовательского интерфейса
    JS определяет, какой интерфейс отображать и как стилизовать страницу. Как правило, обновления пользовательского интерфейса инициируются стороной JS, и большой объем данных и структур пользовательского интерфейса синхронизируется с собственной стороной. Такие обновления часто вызывают проблемы с производительностью, особенно когда интерфейс сложный и данные Большое количество изменений, сложная анимация и высокая частота изменений
  • Ответ на событие пользовательского интерфейса + обновление пользовательского интерфейса
    Если обновление UI не сильно меняет, то проблема не большая
    Если событие пользовательского интерфейса запускает обновление пользовательского интерфейса, а логика является сложной и трудоемкой, может быть разница во времени между синхронизацией данных между JS-стороной и нативной стороной, что может вызвать проблемы с производительностью.

Подводя итог, можно сказать, что основные точки оптимизации производительности RN относительно ясны.

  • Оптимизация рендеринга первого экрана: обработка размера пакета JS Bundle, сжатие файлов и кэширование.
  • Оптимизация обновления пользовательского интерфейса
    • Уменьшите обновления или объединить несколько обновлений
    • Улучшить отзывчивость компонентов:
      • setNativeProps напрямую обновляет свойства Native-компонента на нижнем уровне (фактически это не решает проблему синхронизации данных между JS-стороной и Native-стороной)
      • Немедленно выполнить обратный вызов обновления
    • Оптимизация анимации
      • При использовании библиотеки классов Animated обновление отправляется на нативную сторону одновременно, и нативная сторона несет ответственность за само обновление.
      • Поместите некоторые трудоемкие операции после анимации и обновлений пользовательского интерфейса.
  • Другие оптимизации (уровень кода)

c6c076d9-2dae-4c56-b382-83b7f268814a

Каждая маленькая точка будет в основном написана в порядке, который легко реализовать и выполнить.

1. Делать ли повторный рендеринг - shouldComponentUpdate

Пожалуйста, обратитесь к официальной документации по жизненному циклуReact.Component - React

Изменения состояния и реквизита в реагирующих приложениях вызовут повторный рендеринг.

Рассмотрим следующую ситуацию

class Home extends Component<Props> {
  constructor(props) {
    super(props);
    this.state = {
      a: '点我看看会不会re-render',
    }
  }

  render() {
    console.log('重新渲染   re-render------------------');
    return (
      <View style={styles.container}>
        <TouchableOpacity style={styles.addBtn} onPress={() => this.setState({ a: this.state.a })}>
          <Text>{this.state.a}</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

Основной кодthis.setState({ a: this.state.a })
2018-04-30 16 59 13

Очевидно, что нет никаких изменений в a, просто setState, и это напрямую запускает повторный рендеринг.Представьте, если на странице много данных, сколько потерь производительности это вызовет.

плюсshouldComponentUpdateкрючок, чтобы увидеть, как

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.a !== this.state.a
  }

2018-04-30 17 00 54

Ну, теперь лучше, никакого безмозглого рендеринга

Так что, если это ссылочный объект?

const obj = { num: 1 };
class Home extends Component<Props> {
  constructor(props) {
    super(props);
    this.state = {
      b: null
    }
  }

  componentWillMount() {
    this.setState({
      b: obj
    })
  }

  render() {
    console.log('重新渲染   re-render------------------');
    return (
      <View style={styles.container}>
        <TouchableOpacity style={styles.addBtn} onPress={() => {
          obj.num++;
          this.setState({
            b: obj
          })
        }}>
          <Text>{this.state.b.num}</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

Give b всегда указывает на один и тот же ссылочный объект obj, хотя каждый раз, когда вы щелкаете, obj.num будет изменяться
Однако будет ли страница перерисовываться?
продолжайте смотреть на картинку
2018-04-30 17 15 49

Отлично, содержимое объекта изменилось и страница перерисовалась

затем добавьтеshouldComponentUpdateКак насчет сравнения?

shouldComponentUpdate(nextProps, nextState) {
    return nextState.b !== this.state.b
  }

2018-04-30 17 17 51

Страница не меняется
Причина: b каждый раз указывает на один и тот же ссылочный объект obj, а ссылочный адрес не меняется.shouldComponentUpdateЭто будет только поверхностное сравнение, оно, естественно, вернет false, и страница не будет повторно отображаться.

Это должно быть хорошо объяснено здесь.shouldComponentUpdateспециальность

Итак, как обработать случай ссылки на объекты? В настоящее время наиболее уважаемым методом является использование неизменяемых объектов immutablejs, которые создаются самой facebook.
GitHub - facebook/immutable-js
Хорошо, давайте проведем исследование

Кроме того, существуетpureComponent, просто прочитайте официальное введение
React Top-Level API - React

React.PureComponent React.PureComponent похож на React.Component, разница между ними в том, что React.Component не реализует shouldComponentUpdate(), но React.PureComponent реализует его с поверхностным сравнением свойств и состояний.

Если функция render() вашего компонента React отображает тот же результат с теми же реквизитами и состоянием, вы можете использовать React.PureComponent для повышения производительности в некоторых случаях.

Note

Метод shouldComponentUpdate() в React.PureComponent только поверхностно сравнивает объекты. Если они содержат сложные структуры данных, это может привести к ложноотрицательным результатам для более глубоких различий. Расширяйте PureComponent только тогда, когда вы ожидаете иметь простые реквизиты и state или используйте forceUpdate(), если вы знаете, что глубинные структуры данных изменились, или рассмотрите возможность использования неизменяемых объектов для облегчения быстрого сравнения вложенных данных.

Кроме того, функция shouldComponentUpdate() в React.PureComponent пропускает обновления свойств для всего поддерева компонентов.Убедитесь, что все дочерние компоненты также являются «чистыми».

В конце концов, он просто автоматически используетshouldComponentUpdateЭто обычный компонент хука, ничего особенного.
image

2. Скорость ответа компонента (InteractionManager, requestAnimationFrame, setNativeProps)

1) взаимодействиеманагеру

InteractionManagerа такжеrequestAnimationFrame(fn)Функция аналогична, как во избежание зависания анимации, так и в том, что анимация выполняется во время рендеринга, или есть много вычислений кода, которые блокируют процесс страницы.
InteractionManager.runAfterInteractionsвыполняется после окончания анимации или операции

InteractionManager.runAfterInteractions(() => {
  // ...long-running synchronous task...
});

2) запрос анимации кадра

window.requestAnimationFrame — интерфейс веб-API | MDN
Используйте requestAnimationFrame(fn) для немедленного выполнения обратного вызова в следующем кадре, чтобы он мог быть асинхронным для повышения скорости отклика компонента;

OnPress() {
  this.requestAnimationFrame(() => {
    // ...setState操作
  });
}

а такжеsetImmediate/setTimeout(): это относительно примитивный метод запуска, который может повлиять на плавность анимации.

3) сетнативепропс

Direct Manipulation · React Native
Свойства собственного компонента напрямую обновляются на нижнем уровне с помощью Direct Manipulation, что позволяет избежать больших накладных расходов, вызванных визуализацией структуры компонента и синхронизацией слишком большого количества изменений представления.

Это действительно принесет определенное улучшение производительности, но также затруднит понимание логики кода и не решит проблему накладных расходов на синхронизацию данных со стороны JS на сторону Native.

Поэтому этот метод больше официально не рекомендуется, более рекомендуемым является разумное использование методов setState() и shouldComponentUpdate() для решения таких проблем.

Use setNativeProps when frequent re-rendering creates a performance bottleneck Direct manipulation will not be a tool that you reach for frequently; you will typically only be using it for creating continuous animations to avoid the overhead of rendering the component hierarchy and reconciling many views. setNativeProps is imperative and stores state in the native layer (DOM, UIView, etc.) and not within your React components, which makes your code more difficult to reason about. Before you use it, try to solve your problem with setState and shouldComponentUpdate.

3. Анимация

Предпосылкой Animated является минимизация ненужных анимаций.Пожалуйста, обратитесь к официальной документации для конкретного использования.Animated · React Native

если ты чувствуешьAnimatedВычисление очень хлопотное, например, некоторые простые операции, такие как свертывание, увеличение или уменьшение вида, изменение размера и т. д., вы можете использоватьLayoutAnimationдля плавного завершения одноразовых анимаций
смотреть прямоsetStateи использоватьLayoutAnimationСравнение после эффекта

непосредственныйsetState
2018-06-11 10 26 44

LayoutAnimationЭффект 1
2018-06-11 09 58 06
LayoutAnimationЭффект 2
2018-06-11 10 30 38

Он очень прост в использовании, разделен на два случая

  • использовать эффект по умолчанию
    существуетcomponentWillUpdateВ хуке должны действовать все анимации всего компонента, либо в отдельных анимацияхsetStateметод, использовавшийся ранееLayoutAnimation.spring();
componentWillUpdate() {
    // spring, easeInEaseOut, linear
    LayoutAnimation.linear();
  }
  • Используйте пользовательские эффекты
componentWillUpdate() {
    LayoutAnimation.configureNext(config)
  }
const config = {
  duration: 500, // 动画时间
  create: {
  // spring,linear,easeInEaseOut,easeIn,easeOut,keyboard
    type: LayoutAnimation.Types.linear,
  // opacity,scaleXY 透明度,位移
    property: LayoutAnimation.Properties.opacity,
  },
  update: {
  // 更新时显示的动画
    type: LayoutAnimation.Types.easeInEaseOut,
  }
};

(Продолжение следует...)

постскриптум

Спасибо за ваше терпение, чтобы увидеть здесь, и я надеюсь, что вы узнаете что-то!

Если вы не очень заняты, пожалуйста, закажите звезду⭐[Портал блога Github], это большое поощрение для автора.

Мне нравится делать записи во время моего учебного процесса. Я разделяю некоторые из моих накоплений и мышлением на передней дороге. Я надеюсь общаться и добиться прогресса с вами.