Несколько дней назад, мы написали пост о приближающихся изменениях в наших lifecycle-методах, где также затронули стратегию постепенной миграции (обновления). В React 16.3.0 мы добавили несколько новых lifecycle-методов для помощи в этой миграции. Мы также предоставили новое API для давно ожидаемых новшеств: официальное context API, ref forwarding API и ergonomic ref API.


Официальное Context API


Много лет React предлагал экспериментальное API для работы с контекстом. Пусть это и была мощная "штука", использование такого API было под угрозой, так как мы всё хотели заменить "экспериментальное" API.


Версия 16.3 представляет новое context API, которое эффективнее и поддерживает сразу как проверку статичных типов (static type checking) так и глубокие обновления (deep updates).


Старое context API будет работать для всех релизов 16й версии, поэтому у вас есть время мигрировать.

Ниже пример, в котором показано как можно "прокинуть" тему оформления (theme), используя новое API:


const ThemeContext = React.createContext('light');

class ThemeProvider extends React.Component {
  state = {theme: 'light'};

  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

class ThemedButton extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>
        {theme => <Button theme={theme} />}
      </ThemeContext.Consumer>
    );
  }
}

Больше о context API здесь.


createRef API


React предоставлял два способа управления refs: указание ref обычной строкой и callback-вызов. Хотя и указание ref просто в качестве строки было удобнее, это имело несколько минусов и поэтому мы рекомендовали использовать вариант с callback'ом.


Версия 16.3 добавляет новую опцию для управления refs, которая предлагает удобство указания ref в виде строки без недочетов:


class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

Callback refs будут поддерживаться и далее в новом createRef API. Не спешите заменять callback refs в ваших компонентах. Они более гибкие, поэтому мы оставим их для будущего продвинутого использования.

Больше о createRef API здесь.


forwardRef API


Компоненты высшего порядка (или HOC) — удобный инструмент для "переиспользования" кода между компонентами. Если взять за основу предыдущий пример с context, мы можем создать HOC в который "прокинем" theme в качестве свойства (props):


function withTheme(Component) {
  return function ThemedComponent(props) {
    return (
      <ThemeContext.Consumer>
        {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

Мы можем использовать этот HOC, чтобы связывать компоненты со свойствами темы оформления (theme) из контекста (context) без использования ThemeContext напрямую. Для примера:


class FancyButton extends React.Component {
  buttonRef = React.createRef();

  focus() {
    this.buttonRef.current.focus();
  }

  render() {
    const {label, theme, ...rest} = this.props;
    return (
      <button
        {...rest}
        className={`${theme}-button`}
        ref={this.buttonRef}>

        {label}
      </button>
    );
  }
}

const FancyThemedButton = withTheme(FancyButton);

// Мы можем отрисовать FancyThemedButton, как будто это FancyButton
// Компонент автоматически получит текущую тему (*свойство theme*)
// И HOC прокинет его вниз через props.
<FancyThemedButton
  label="Click me!"
  onClick={handleClick}
/>;

Обычно HOC'и прокидывают свойства (props) в компонент, который они оборачивают. К сожалению, refs не прокидываются. Это значит, что мы не можем прикрепить ref к FancyButton когда используем FancyThemedButton. Выходит, вызвать focus() невозможно.


forwardRef API решает эту проблему, предлагая перехватить ref и послать его дальше как обычное свойство.


function withTheme(Component) {
  // обратите внимание, что "ref" предоставлен нам React.forwardRef.
  function ThemedComponent(props, ref) {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <Component {...props} ref={ref} theme={theme} />
        )}
      </ThemeContext.Consumer>
    );
  }

  // Следующие несколько строк кода необязательны,
  // они нужны чтобы дать компоненту понятное имя в DevTools,
  // например, "ForwardRef(withTheme(MyComponent))"
  const name = Component.displayName || Component.name;
  ThemedComponent.displayName = `withTheme(${name})`;

  //  Просим React прокинуть ref в ThemedComponent.
  return React.forwardRef(ThemedComponent);
}

const fancyButtonRef = React.createRef();

// Сейчас fancyButtonRef указывает на FancyButton
<FancyThemedButton
  label="Click me!"
  onClick={handleClick}
  ref={fancyButtonRef}
/>;

Изменения в методах жизненного цикла (lifecycle methods)


API создания компонентов с помощью class практически не изменялось несколько лет. Однако, мы добавили поддержку новых "фич" (таких как error boundaries или будущий async rendering mode) к которым данная модель была не готова как следует.


Например, с текущим API очень просто заблокировать начальную отрисовку компонента (initial render) несущественной логикой. Отчасти, это связано с тем, что существует несколько вариантов для решения поставленных задач, и поэтому выбрать лучший не просто. Мы заметили, что обработке ошибок часто не уделяется внимание и это может приводить к утечкам памяти (что в свою очередь пагубно скажется в будущем на асинхронной отрисовке (async rendering mode)). Текущее class API так же осложняет другие наши задумки, например работу над прототпированием React компилятора.


Большинство из таких проблем связано со следующими lifecycle-методами: componentWillMount, componentWillUnmount и ComponentWillUpdate. Так же эти методы вносят наибольшую путаницу в React коммьюнити. Поэтому, мы собираемся отказаться них в пользу лучшей альтернативы.


Конечно, мы понимаем, что такие изменения затронут огромное количество существующих компонентов. Поэтому миграция будет постепенной настолько, насколько это возможно. Мы также предложим пути отступления (У нас в Facebook — 50,000 компонентов. Мы так же нуждаемся в постепенном обновлении).


Deprecation (устаревший метод) предупреждение будет включено в будущих 16.x релизах, но поддержка текущих "жизненных циклов" будет работать до версии 17. Так же, в семнадцатой версии вы сможете использовать их с префиксом UNSAFE_. Мы приготовили автоматический скрипт для переименования.

В дополнении к будущим "deprecated" методам жизненного цикла, мы добавили парочку новых:


  • getDerivedStateFromProps — альтернатива для componentWillReceiveProps
  • getSnapshotBeforeUpdate — альтернатива для безопасного чтения свойств из DOM, прежде чем случится обновление.

Больше об изменении в lifecycle здесь.


StrictMode компонент


StrictMode — это инструмент для нахождения потенциальных проблем в вашем приложении. Как и Fragment, StrictMode визуально не отрисовывается. Этот компонент активирует дополнительные проверки и предупреждения.


StrictMode работает только в development режиме (в режиме разработки, не в "проде")

Несмотря на то, что невозможно поймать все проблемные места (например, некоторые типы мутаций), StrictMode компонент поможет во многих ситуациях. Если вы видите предупреждения в "строгом режиме" (strict mode), значит скорее всего у вас будут баги в асинхронном режиме отрисовки (async rendering mode).


В версии 16.3, StrictMode умеет:


  • находить компоненты, использующие небезопасные (старые) lifecycle-методы
  • предупреждать об использовании старого API обозначения ref как строки
  • выслеживать непредвиденные side-эффекты

Дополнительный функционал будет добавлен в будущих релизах React.


Больше о StrictMode здесь.

Комментарии (3)


  1. Nikulio
    30.03.2018 13:48
    +1

    Спасибо за оперативность!


  1. vintage
    31.03.2018 13:31

    У нас в Facebook — 50,000 компонентов. Мы так же нуждаемся в постепенном обновлении

    Типичный цикл развития ПО:


    1. Берём технологию попроще, не задумываясь об архитектуре. Обосновывается эта лень конечно же таким великими принципами как KISS и YAGNI.
    2. Пишем тонны копипасты. Некогда рефакторить, надо фигачить фичи.
    3. Приходит понимание как надо было сделать, но на рефактеринг всей копипасты нужна пара человеко-жизней, которых никакой бизнес никогда не даст.
    4. Вкручиваем костыли. Пытаемся сохранять обратную совместимость.
    5. Подрастает новое поколение, которым лень разбираться в чужих костылях.
    6. Переходим к пункту (1).

    Реально, по моей оценке, на весь фейсбук, если бы он был разработан на полноценных компонентах без копипасты, хватило бы тысячи компонент.


    1. 3um
      02.04.2018 11:17

      Может оно и так, но бизнес не даёт не только времени, но и денег, так что кто-то запускает 5 проектов, 1 из которых не закроется, а кто-то пишет свой единственный, зато красиво.