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

React и данные: Эффективные способы хранения и ...

iadramelk
November 18, 2017

React и данные: Эффективные способы хранения и изменения стейта

iadramelk

November 18, 2017
Tweet

More Decks by iadramelk

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