Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Алексей Иванов

FrontFest
November 21, 2017

Алексей Иванов

FrontFest

November 21, 2017
Tweet

More Decks by FrontFest

Other Decks in Programming

Transcript

  1. Дерево из нескольких сотен компонентов тормозит если его специально не

    оптимизировать: —  паузы при вводе данных в <input /> , —  паузы перед выставлением чекбоксов; —  паузы при открытии попапов. Проблема 9
  2. —  Чтобы понять что обновилось на странице надо пересчитать VirtualDOM.

    —  Пересчет и сравнение VirtualDOM не бесплатное. —  Не всегда можно избежать вставок нового HTML. Чтобы уменьшить количество вставок и пересчетов надо понимать как React работает. Почему? 10
  3. // В JSX <div className='cn'> Content! </div> // В JavaScript

    React.createElement( 'div', { className: 'cn' }, 'Content!' ); React Element КАК РАБОТАЕТ РЕНДЕР В REACT 12
  4. // В JSX <div className='cn'> Content 1! <br /> Content

    2! </h1> // В JavaScript React.createElement( 'div', { className: 'cn' }, 'Content 1!', React.createElement('br'), 'Content 2!' ) React Element КАК РАБОТАЕТ РЕНДЕР В REACT 13
  5. Допустимые значения: —  false, null, undefined, и true; —  строки;

    —  массивы; —  React Element; —  React Component. React Element children КАК РАБОТАЕТ РЕНДЕР В REACT 14
  6. function Table({ rows }) { return rows .map(row => (

    <tr key={row.id}> <td>{row.title}</td> </tr> )); }; React Component КАК РАБОТАЕТ РЕНДЕР В REACT 15
  7. // В JSX <Table rows={rows} /> // В JavaScript React.createElement(

    Table, { rows: rows } ) React Component КАК РАБОТАЕТ РЕНДЕР В REACT 16
  8. function Table({ rows }) { /* ... */ } ReactDOM.render(

    React.createElement(Table, { rows: rows }), document.getElementById('#root') ); Вставка компонента на страницу КАК РАБОТАЕТ РЕНДЕР В REACT 17
  9. function Table({ rows }) { /* ... */ } ReactDOM.render(

    React.createElement(Table, { rows: rows }), document.getElementById(' #root ') ); Вставка компонента на страницу КАК РАБОТАЕТ РЕНДЕР В REACT 18
  10. function Table ({ rows }) { /* ... */ }

    ReactDOM.render( React.createElement( Table , { rows: rows }), document.getElementById('#root') ); Вставка компонента на страницу КАК РАБОТАЕТ РЕНДЕР В REACT 19
  11. // Вызов функции React.createElement( Table, { rows: rows } );

    // Результат вызова { type: Table, props: { rows: rows }, // ... } Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 20
  12. // Вызов функции React.createElement( Table, { rows: rows } );

    // Результат вызова { type : Table, props: { rows: rows }, // ... } Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 21
  13. // Вызов функции React.createElement( Table, { rows: rows } );

    // Результат вызова { type: Table, props : { rows: rows }, // ... } Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 22
  14. // Вызов функции React.createElement( 'div', { className: 'cn' }, 'Content!'

    ); // Результат вызова { type: 'div', props: { className: 'cn', children: 'Content!' } } Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 23
  15. // Вызов функции React.createElement( 'div', { className: 'cn' }, 'Content!'

    ); // Результат вызова { type: 'div', props: { className: 'cn', children : 'Content!' } } Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 24
  16. // Вызов функции React.createElement( 'div', {}, 'Content 1!', 'Content 2!',

    ); // Результат вызова // ... props: { children : [ 'Content 1!', 'Content 2!' ] // ... Вызов React.createElement КАК РАБОТАЕТ РЕНДЕР В REACT 25
  17. ReactDOM.render( React.createElement(Table, { rows: rows }), document.getElementById(' #root ') );

    ReactDOM.render() повторно вызванный на старом элементе запускает процесс сверки VirtualDOM дерева (reconcilation). Обновление страницы КАК РАБОТАЕТ РЕНДЕР В REACT 27
  18. // Было { type: ' div ', props: { className:

    'cn', children: 'Content!' } } // Стало { type: ' span ', props: { className: 'cn', children: 'Content!' } } Разный type — замена ноды КАК РАБОТАЕТ РЕНДЕР В REACT 28
  19. // Было { type: 'div', props: { className: ' cn

    ' }, } // Стало { type: 'div', props: { className: ' cnn ' }, } Element + новые props — обновление КАК РАБОТАЕТ РЕНДЕР В REACT 29
  20. // Было { type: Table , props: { // ...

    }, } // Стало { type: Table , props: { // ... }, } Старый Component — перезапуск рендера КАК РАБОТАЕТ РЕНДЕР В REACT 30
  21. // .. props: { children : [ // elements ]

    }, / ... В зависимости от наличия key : —  если нет – по очереди сверху вниз, —  если есть – по ключам. массив children КАК РАБОТАЕТ РЕНДЕР В REACT 31
  22. class App extends Component { state = { counter: 0

    } increment = () => this.setState({ counter: this.state.counter + 1, }) render = () => (<button onClick={this.increment}> {'Counter: ' + this.state.counter} </button>) } Компоненты и state КАК РАБОТАЕТ РЕНДЕР В REACT 32
  23. class App extends Component { state = { counter: 0

    } increment = () => this.setState({ counter: this. state .counter + 1, }) render = () => (<button onClick={this.increment}> {'Counter: ' + this. state .counter} </button>) } Компоненты и state КАК РАБОТАЕТ РЕНДЕР В REACT 33
  24. class App extends Component { state = { counter: 0

    } increment = () => this. setState ({ counter: this.state.counter + 1, }) render = () => (<button onClick={this.increment}> {'Counter: ' + this.state.counter} </button>) } Компоненты и state КАК РАБОТАЕТ РЕНДЕР В REACT 34
  25. this.state – запускает ререндер не всей страницы, а только текущего

    компонента и его детей. Компоненты и state КАК РАБОТАЕТ РЕНДЕР В REACT 35
  26. —  React Devtools + Highlight updates —   /?react_perf —

     Chrome Devtools / Rendering / Paint Flashing Тестирование и дебаг DEMO TIME SUMMARY 37
  27. <div> <Message /> <Table /> </div> // ... props: {

    children : [ { type: Message }, { type: Table }, ] } // ... Массивы без ключей ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 39
  28. // Было <div> <Message /> <Table /> </div> // Стало

    <div> <Table /> </div> Массивы без ключей ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 40
  29. // Было props: { children : [ { type: Message

    }, { type: Table }, ] } // ... // Стало props: { children : { type: Table } } // ... Массивы без ключей ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 41
  30. // Используем && <div> { isShown && <Message /> }

    <Table /> </div> // ... props: { children: [ false, { type: Table }, ] } // ... Массивы без ключей ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 42
  31. HOC — функция принимающая на вход компонент и возвращающая другой

    компонент. function withName( Component ) { // Вычисляется name return < Component name={name} />; } Hight Order Components ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 43
  32. class App extends React.Component() { render() { // Создает новый

    объект при каждом рендере const ComponentWithName = withName(Component); return < ComponentWithName />; } } HOC внутри рендера. Плохо ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 44
  33. // Было { // Старый компонент type: ComponentWithName , props:

    {}, } // Стало { // Новый компонент type: ComponentWithName , props: {}, } HOC внутри рендера. Плохо ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 45
  34. // Создает новый объект только один раз const ComponentWithName =

    withName(Component); class App extends React.Component() { render() { return < ComponentWithName />; } } HOC вне рендера. Хорошо ОПТИМИЗАЦИЯ ФИЗИЧЕСКОГО РЕРЕНДЕРА 46
  35. —  componentWillReceiveProps() —   shouldComponentUpdate() —  componentWillUpdate() —  render() —

     componentDidUpdate() Жизненный цикл компонента ОПТИМИЗАЦИЯ VIRTUALDOM 49
  36. class TableRow extends React.Component { shouldComponentUpdate (nextProps, nextState) { const

    { props, state } = this; return !shallowEqual(props, nextProps) && !shallowEqual(state, nextState); } render() { /* ... */ } } shouldComponentUpdate() ОПТИМИЗАЦИЯ VIRTUALDOM 50
  37. class TableRow extends React.Component { shouldComponentUpdate(nextProps, nextState) { const {

    props, state } = this; return ! shallowEqual (props, nextProps) && ! shallowEqual (state, nextState); } render() { /* ... */ } } shouldComponentUpdate() ОПТИМИЗАЦИЯ VIRTUALDOM 51
  38. // Так PureComponent не будет работать <Table rows={rows.map(/* ... */)}

    style={ { color: 'red' } } onUpdate={() => { /* ... */ }} /> Фунции, объекты и массивы ОПТИМИЗАЦИЯ VIRTUALDOM 54
  39. —   shouldComponentUpdate может выполняться дольше рендера – не делайте

    все компоненты PureComponent . —  Только ключевые компоненты на пути апдейта. Когда и где использовать shouldComponentUpdate DEMO TIME 2.0 SUMMARY 56
  40. —  Изменения элемента массива или объекта всегда меняют всех его

    родителей. Изменение родителей ведет к перерендеру. Чем меньше родителей тем лучше. —  Чем ниже в дереве компонентов происходит апдейт, тем меньшую часть дерева надо перерендеривать. —  Чем проще props, тем быстрее их сравнивать через === . Общие требования к передаче ОРГАНИЗАЦИЯ ДАННЫХ 61
  41. —  Искать в массиве нужную запись через .find() долго и

    дорого. Удобнее сразу обращаться по ключу. —  Работать с вложенными коллекциями сложно – лучше их вынести на один уровень и работать по отдельности. Общие требования к обновлению ОРГАНИЗАЦИЯ ДАННЫХ 62
  42. { posts: [ { id: 1, comments: [ { id:

    1, /* ... */ }, { id: 2, /* ... */ }, ] }, ] } Плохая структура ОРГАНИЗАЦИЯ ДАННЫХ 63
  43. { posts: { 1: { id: 1, comments: [ 1,

    2 ] } }, comments: { /* ... */ } } Хорошая структура ОРГАНИЗАЦИЯ ДАННЫХ 64
  44. { posts: { 1: { id: 1, comments : [

    1, 2 ] } }, comments : { /* ... */ } } Хорошая структура ОРГАНИЗАЦИЯ ДАННЫХ 65
  45. https://github.com/paularmstrong/normalizr —  Берем схему и плохую структуру. —  На выходе

    получаем хорошую. —  Перед выводом в шаблон можно сделать обратную денормализацию. Хорошая структура из плохой ОРГАНИЗАЦИЯ ДАННЫХ 66
  46. Реакт позволяет прокидывать данные минуя промежуточные уровни используя context .

    Этим пользуются почти все популярные библиотеки: Redux, MobX, React Intl, React Router. Внимание! Не пытайтесть использовать сами, очень кривое и поломанное API. Привязка данных ко вложенным компонентам ОРГАНИЗАЦИЯ ДАННЫХ 67
  47. this.context – глобальные переменные в React. < Provider store={store}> <Component>

    {connect(mapStateToProps)(ChildComponent)} </Component> </ Provider > Принцип работы контекста ОРГАНИЗАЦИЯ ДАННЫХ 68
  48. this.context – глобальные переменные в React. <Provider store={store}> <Component> {

    connect (mapStateToProps)(ChildComponent)} </Component> </Provider> Принцип работы контекста ОРГАНИЗАЦИЯ ДАННЫХ 69
  49. —  Достает this.context.store . —  Подписывается на обновления. —  При

    обновлении запускает селектор из mapStateToProps . —  Сравнивает новые props со старыми через shallowEqual . —  Изменились? Дергает this.setState() и ререндерит обернутый компонент. Упрощенный алгоритм connect() ОРГАНИЗАЦИЯ ДАННЫХ 70
  50. const ConnectedFilters = connect( state => { return { filters:

    state.filters }; })(FIlters); connect для фильтров ОРГАНИЗАЦИЯ ДАННЫХ 72
  51. { productIds : [ 1, 2, 3 ] products: {

    1: { /* ... */ }, 2: { /* ... */ }, }. } connect для таблицы ОРГАНИЗАЦИЯ ДАННЫХ 74
  52. const ConnectedTable = connect( state => { return { productIds:

    state.productIds }; })(Table); const Table = ({ productIds }) => { return productIds.map(id => <ConnectedRow id={id} />); } connect для таблицы ОРГАНИЗАЦИЯ ДАННЫХ 75
  53. const ConnectedTable = connect(state => { return { productIds: state.productIds

    }; })(Table); const Table = ({ productIds }) => { return productIds.map(id => < ConnectedRow id={id} />); } connect для таблицы ОРГАНИЗАЦИЯ ДАННЫХ 76
  54. const ConnectedRow = connect((state, ownProps ) => { return state.products[

    ownProps .id]; })(Row); connect для таблицы ОРГАНИЗАЦИЯ ДАННЫХ 77
  55. { products: { 1: { id: 1, variations: [ 1,

    2 ], } }, variations: { /* ... */ } } Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 78
  56. Три варианта: 1.  Оставить как есть и делать коннект уровнем

    ниже. 2.  Если нужны произвольные данные простого типа – собирать в селекторе. Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 79
  57. Cобирать в селекторе: const ConnectedPost = connect((state, ownProps) => {

    const post = state.posts[ownProps.id]; const commentLink = getCommentLink(state, post.comments); return { ...post, commentLink }; })(Row); Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 80
  58. Три варианта: 1.  Оставить как есть и делать коннект уровнем

    ниже. 2.  Если нужны произвольные данные простого типа – собирать в селекторе. 3.  Если нужны произвольные данные сложного типа – areStatePropsEqual . Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 81
  59. Используем areStatePropsEqual: // Сложный объект с использованием .map() или Object.assing()

    function prepareConnectedPost(state, id) { /* ... */ } const ConnectedPost = connect((state, ownProps) => { return prepareConnectedPost(state, ownProps.id); })(Row); Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 82
  60. Используем areStatePropsEqual: // Сложный объект с использованием .map() или Object.assing()

    function prepareConnectedPost (state, id) { /* ... */ } const ConnectedPost = connect((state, ownProps) => { return prepareConnectedPost (state, ownProps.id); })(Row); Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 83
  61. Используем areStatePropsEqual: connect( mapStateToProps, mapDispatchToProps, mergeProps, options )(Component) Что с

    вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 84
  62. Используем areStatePropsEqual: const options = { areStatePropsEqual : (oldProps, newProps)

    => { return shallowEqual(oldProps, newProps); }, }; Что с вложенными коллекциями? ОРГАНИЗАЦИЯ ДАННЫХ 85
  63. Выносим селектор в редюсер и кешируем результат: { postIds: [

    /* ... */ ], posts: { /* ... */ }, cachedPosts : { /* ... */ }, } Что делать если селекторов много и они дорогие? ОРГАНИЗАЦИЯ ДАННЫХ 86
  64. + Очень быстрое сравнение. + Не меняются, если не изменились

    зависимые данные. – Две копии данных в строе. – Легко забыть обновить. – Разные версии под разные компоненты. Кеширование селекторов ОРГАНИЗАЦИЯ ДАННЫХ 87
  65. —  Обязательно read-only. —  В связаных коллекция оставлять ссылку на

    основную – если после обновления комментария нужно обновить в пост, то в коллекцию comment имеет смысл добавить postId . —  Разбиваем генерацию по уровням – comment дергает post , post может дергать что‑то еще. Правила работы с кешем ОРГАНИЗАЦИЯ ДАННЫХ 88
  66. 1.  Сначала профилирование: React DevTools, /?react_perf. 2.   Тормозит? Смотрим

    базовые ошибки и shouldComponentUpdate. 3.   Еще тормозит? Разносим коннекты и нормализируем данные. 4.   Еще торомзит? Пробуем кешировать результат селекторов. 5.  Радуемся. Или плачем. Заключение 91