Как будет реагировать 0.14 Безстоящие компоненты предлагают улучшения производительности без mustComponentUpdate?

Этот вопрос крутился в моей голове, так как я читал заметки о выпуске (и другую связанную рекламу) вокруг React 0.14 - я большой поклонник React, и я думаю, что компоненты без гражданства (https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components) - отличная идея, как для удобства написания таких компонентов, так и для выражения в коде намерение, что эти компоненты должны быть "чистыми" в плане рендеринга последовательно для тех же реквизитов данных.

Возникает вопрос: как будет возможно, чтобы React оптимизировал эти функции безстоящих компонентов, не обращаясь к целиком и полагая, что ссылки на реквизиты не только неизменны, потому что им не следует манипулировать внутри компонента, но также и то, что они никогда не может измениться за пределами жизненного цикла компонента? Причина, по которой "обычные" компоненты (например, компоненты с состоянием - другими словами, компоненты, которые проходят весь жизненный цикл, компонентWillMount, getInitialState и т.д.) Имеют необязательную функцию "shouldComponentUpdate", так это то, что React не предполагает, что все реквизиты и государственные ссылки абсолютно неизменяемы. После того, как компоненты были обработаны, некоторые свойства ссылок реквизита могут измениться, и поэтому один и тот же экземпляр "реквизит" может иметь другое содержимое позже. Частично это вызвало много волнений по поводу использования полностью неизменяемых структур и почему было сказано, что использование Om с React могло бы принести большие выигрыши в производительности; потому что неизменяемые структуры, используемые там, гарантировали, что любой данный экземпляр какого-либо объекта никогда не может быть мутирован, поэтому mustComponentUpdate может выполнять действительно дешевые проверки равенства ссылок на реквизитах и ​​состоянии (http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/).

Я пытался узнать больше об этом, но не получил нигде. Я не могу предусмотреть, какие улучшения производительности могут быть сделаны вокруг компонентов без гражданства, не предполагая, что данные реквизита будут состоять из неизменяемых типов. Возможно, некоторый предварительный анализ типов неизменяемых реквизитов, чтобы попытаться угадать, представляют ли "реквизиты" и "nextProps" те же данные?

Я просто задавался вопросом, есть ли у кого-то какая-либо внутренняя информация или какое-то иное просветляющее понимание этого. Если React действительно начал требовать, чтобы типы реквизитов были "полностью неизменными" (позвольте сравнению сравнения ссылок, чтобы подтвердить, что данные не изменились), я думаю, что это был бы большой шаг вперед, но это также может быть большим изменением.

Ответ 1

Избегание ненужных распределений

Если я правильно понимаю, функциональные компоненты без гражданства преобразуются в обычные компоненты. Из источник:

function StatelessComponent(Component) {
}
StatelessComponent.prototype.render = function() {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  return Component(this.props, this.context, this.updater);
};

Когда создается экземпляр компонента без состояния, выделяется новый объект. Этот новый объект имеет методы жизненного цикла, такие как componentWillMount и componentWillReceiveProps. Я предполагаю, что план не создает эти объекты вообще. Не создавать объекты избегают ненужных распределений.

Избегайте ненужных проверок

Для реализации методов жизненного цикла требуется ряд проверок, таких как:

if (inst.componentWillUpdate) {
  inst.componentWillUpdate(nextProps, nextState, nextContext);
}

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

ИЗМЕНИТЬ

Убрано вещество в memoization, которое не отвечало на вопрос или хорошо объясняло.

Ответ 2

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

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

На странице React v0.14 Release Candidate, Бен Алперт утверждает:

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

Я уверен, что он имел в виду кешируемость чистых функциональных компонентов.

Вот очень простая реализация кэша вперед для демонстрационных целей:

let componentA = (props) => {
  return <p>{ props.text }</p>;
}

let cache = {};
let cachedA = (props) => {
  let key = JSON.stringify(props); // a fast hash function can be used as well
  if( key in cache ) {
    return cache[key];
  }else {
    cache[key] = componentA(props);
    return cache[key];
  }
}

И есть другие хорошие свойства чистых функциональных компонентов, о которых я могу думать в данный момент:

  • unit test дружественный
  • более легкий, чем компоненты на основе классов.
  • очень многократно используется, поскольку они являются просто функциями

Ответ 3

Вы можете использовать декоратор для компоновки своих компонентов функции без состояния для выполнения оптимизации высокого порядка, чтобы определить, должен ли React отображать этот компонент или нет. Я использую immutable для выполнения строгих проверок равенства между реквизитами.

Скажем, у нас есть такой компонент:

ClickableGreeter.js

const ClickableGreeter = (props) => (
    <div onClick={(e) => props.onClick(e)}>
        {"Hello " + props.name}
    </div>
)

ClickableGreeter.propTypes = {
    onClick: React.PropTypes.func.isRequired,
    name: React.PropTypes.text.isRequired
}

export default ClickableGreeter;

Мы хотим, чтобы React не отображал его, если имя не изменяется. Я использую простой декоратор, который использует библиотеку immutable для создания неизменяемого представления реквизита и nextProps и выполняет простую проверку равенства:

pureImmutableRenderDecorator.js:

import React from 'react'
import Immutable from 'immutable';

const pureComponent = (Component, propsToRemove = []) => {

    class PureComponent extends React.Component {
        constructor(props) {
            super(props);
            this.displayName = 'PureComponent';
        }
        comparator(props, nextProps, state, nextState) {
            return (
                !Immutable.is(Immutable.fromJS(props), Immutable.fromJS(nextProps)) ||
                !Immutable.is(Immutable.fromJS(state), Immutable.fromJS(nextState))
            )
        }
        removeKeysFromObject(obj, keys) {
            var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target;
        }
        shouldComponentUpdate(nextProps, nextState) {
            let propsToCompare = this.removeKeysFromObject(this.props, propsToRemove),
                nextPropsToCompare = this.removeKeysFromObject(nextProps, propsToRemove);

            return this.comparator(propsToCompare, nextPropsToCompare, this.state, nextState)
        }
        render() {
            return <Component {...this.props} {...this.state} />
        }
    }

    return PureComponent;

}

export default pureComponent;

Затем вы можете создать компонент PureClickableGreeter, выполнив:

const PureClickableGreeter = pureComponent(ClickableGreeter, ['onClick']) 
//we do not want the 'onClick' props to be compared since it a callback

Конечно, использование immutable здесь является излишним, потому что это простое сравнение строк, но как только вам понадобятся некоторые вложенные реквизиты, immutable - это путь. Вы также должны иметь в виду, что Immutable.fromJS() - тяжелая операция, но это хорошо, если у вас нет большого количества реквизитов (и обычно это целая точка функциональных компонентов без состояния: чтобы сохранить как можно больше реквизитов для лучшего разделения кода и повторное использование).