Реагируйте сегодня и завтра (графика) — часть 2

внешний интерфейс JavaScript Программа перевода самородков React.js
Реагируйте сегодня и завтра (графика) — часть 2

из-за этой речиDanДемонстрационных частей очень много, рекомендуется посмотреть видео, если у вас достаточно времени. Студенты, которые хотят посмотреть видео этой статьи, могут проверить эту мою статью:React Conf 2018 Feature — React Today and Tomorrow Part II, видео с китайскими и английскими субтитрами. первая частьSophie AlpertАдрес текстовой версии выступления:Реагируйте сегодня и завтра (графика) — часть 1

Реагируйте сегодня и завтра — часть 2

Привет. Меня зовут Дэн. Я работаю в команде React и впервые на конференции React. (аплодисменты)

Текущие проблемы с React

Только сейчас Софи рассказывает три вопроса, я думаю, что большинство разработчиков столкнутся с этими проблемами во время процесса развития реагирования. Конечно, мы можем решить эти проблемы один за другим. Мы можем попытаться решить эти проблемы самостоятельно. Но на самом деле решить одну из проблем может сделать другие проблемы более серьезными.

2018-11-12 11 58 06

Например, если мы попытаемся решить проблему «пакетного ада», мы можем добавить больше логики в компонент, но наш компонент станет больше и его будет труднее рефакторировать. С другой стороны, если мы попытаемся разделить компоненты на более мелкие части для простоты повторного использования, дерево компонентов будет более вложенным и окажется в «аду обертывания». Наконец, в любом случае использование классов может сбивать с толку.

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

2018-11-12 11 59 14

И как только вы используете компонент класса, вы не сможете разделить его дальше, не создавая «обертывающего ада». На самом деле, это не новая проблема. Если вы используете React в течение нескольких лет, вы, возможно, помните, что когда React впервые появился, он фактически включал решение этой проблемы. Что ж, решение - миксины. Миксины позволяют повторно использовать методы между классами и уменьшают вложенность.

2018-11-13 12 15 16

Итак, мы собираемся добавить миксины обратно в React? (да... нет...) Правильно, нет, нет, мы не будем добавлять примеси. Я имею в виду, что код, который раньше использовал примеси, больше не непригоден для использования. Но мы больше не рекомендуем использовать примеси в React. Если вам интересно, почему мы это делаем, вы можете прочитать статью, которую мы написали ранее в блоге React под названием «Миксины вредны". В статье мы обнаружили по экспериментальным результатам, что примеси вызывают больше проблем, чем решают. Поэтому мы не рекомендуем использовать миксины.

у нас есть предложение

Тогда, возможно, мы не сможем решить эту проблему, потому что она присуща компонентной модели React. Может быть, мы должны принять решение принять реальность. (Смех) Или, может быть, есть другой способ написания компонентов, позволяющий избежать этих проблем.

2018-11-13 12 17 50

Это то, чем я собираюсь поделиться сегодня.

2018-11-13 12 18 30

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

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

2018-11-13 12 23 03

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

2018-11-13 9 25 08

Мы думали о многих способах опубликовать это предложение, возможно, мы могли бы написать предложение, придумать RFC и разместить его там. Но так как у нас всегда есть конференции React, мы решили разместить это предложение на этой конференции.

Демонстрационная сессия

Итак, давайте перейдем к демонстрационному сеансу. (аплодисменты)

2018-11-13 9 39 36

Мой экран уже проецируется на монитор. Извините за технический сбой. Э-э, кто будет использовать этот проектор, чтобы помочь мне. (Смех) А можно я продублирую свой рабочий стол? Пожалуйста. (Я могу) Да. (Смех) Хорошо, но на экране ничего, я ничего не вижу. (Смех) Сейчас это моя проблема. (Аплодисменты.) Ладно, катастрофа миновала. (Смех) Хорошо, позвольте мне немного изменить размер текста. Ты ясно видишь? (Да хорошо.

Пример знакомого компонента класса

Итак, давайте посмотрим, вот обычный React-компонент, это Row-компонент, тут какие-то стили, а дальше рендерится имя человека.

import React from 'react';
import Row from './Row';

export default function Greeting(props) {
  return (
    <section>
      <Row label="Name">
        {props.name}
      </Row>
    </section>
  );
}

Что мы хотим сделать, так это сделать имя редактируемым. Итак, что мы обычно делаем в React? Нам нужно добавить здесь ввод, нам нужно поместить это содержимое в возвращаемый класс, добавить некоторое локальное состояние и позволить состоянию управлять вводом. Это то, что я тоже собираюсь сделать. Это то, что люди обычно делают в эти дни.

я хочу экспортироватьdefault class GreetingнаследоватьReact.Component. Здесь я буду использовать только стабильный синтаксис JavaScript. Далееconstructor(props), super (props). поместите государство здесьnameИнициализируется как Мэри. Далее я объявлю функцию рендеринга, скопирую этот код и вставлю сюда. Мне жаль. OK.

demo1

Я надеюсь, что вместо того, чтобы просто отображать имя, я надеюсь, что здесь можно будет отобразить ввод. Я заменил это на ввод и установил значение ввода наthis.state.name. Затем, когда ввод ввода изменяется, вызовитеthis.handleNameChange, которая является функцией обратного вызова для моего события изменения. Я объявлю это здесь, и когда имя изменится, назовите его, как обычно.setStateметод. Затем установите имяe.target.value. Верно.

demo2

Если я отредактирую... (страница сообщает об ошибке TypeError) Хорошо, я должен пойти и привязать... (Смех) Извините, мне нужно привязать событие event здесь. Хорошо, теперь мы можем отредактировать его, и он отлично работает.

demo3

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

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary'
    }
    this.handleNameChange = this.handleNameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }
  render() {
    return (
      <section>
        <Row label="Name">
          <input
            value={this.state.name}
            onChange={this.handleNameChange}
          />
        </Row>
      </section>
    );
  }

}

Можно ли реализовать эту функциональность с помощью функционального компонента?

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

Ну и еще должна быть callback-функция изменения, которую я объявляю здесьonChangeфункцияhandleNameChange. Я добавляю сюда функцию для обработки события. Здесь я хочу уведомить настройки Reactnameзначение где-то, но опять же, я не уверен, как реализовать эту функциональность в функциональном компоненте. Поэтому я просто вызываю метод с именемsetNameМетоды. Используйте текущее входное значение. Я объявляю это здесь.

import React from 'react';
import Row from './Row';

export default function Greeting(props) {
  const name = ???
  const setName = ???

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}

Ну, поскольку эти две вещи тесно связаны, верно. Один из них находится в состоянииnameтекущее значение переменной, а другая — функция, позволяющая установить состояниеnameПеременная. Поскольку эти две вещи очень связаны, я объединяю их вместе как пару значений.


- const name = ???
- const setName = ???
+ const [name, setName] = ???

Мы получаем их значения вместе откуда-то. Так вот вопрос где их взять? Ответ заключается в том, чтобы получить его из локального состояния React. Итак, как мне получить React для локального состояния в функциональном компоненте? Ну, я непосредственно используюuseStateчто случится. передать начальное состояние вuseStateфункция для указания ее начального значения.

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}

Посмотрим, нормально ли работает программа. Да, это работает нормально.

demo4

(аплодисменты и возгласы)

Итак, давайте сравним два метода. Слева — знакомый компонент класса. Здесь состояние должно быть объектом. Что ж, мы привязываем некоторые обработчики событий к вызову. используется в обработчике событийthis.setStateметод. когда мы звонимsetStateПри вызове метода значение фактически не устанавливается непосредственно в состояние, а состояние объединяется с объектом состояния в качестве параметра. И когда я хочу получить состояние, нам нужно позвонитьthis.state.something.

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary'
    }
    this.handleNameChange = this.handleNameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }
  render() {
    return (
      <section>
        <Row label="Name">
          <input
            value={this.state.name}
            onChange={this.handleNameChange}
          />
        </Row>
      </section>
    );
  }

}

Итак, давайте снова посмотрим на пример справа: нам не нужно использоватьthis.state.somethingчтобы получить состояние. Потому что переменная имени в состоянии уже доступна в функции. Это переменная. Точно так же, когда нам нужно установить состояние, нам не нужно использоватьthis.something. Потому что функции также позволяют нам устанавливать значение имени в пределах своей области. ТакuseStateчто это такое?useStateявляется Крюк.Hook — это функция, предоставляемая React, которая позволяет вам «прицепиться» к некоторым функциям React в функциональном компоненте.. а такжеuseStateЭто первый хук, о котором мы говорили сегодня, и есть еще несколько хуков, за которыми можно последовать. Мы увидим их позже.

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}

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

Хорошо, давайте вернемся к нашему знакомому примеру с классом. Затем мы хотим добавить вторую область. Например, добавьте поле для фамилий. Тогда наша обычная практика заключается в добавлении нового ключа в состояние. Я скопировал и вставил эту строку сюда. Изменить его здесьsurname. рендерить здесь, вотsurnameа такжеhandleSurnameChange. Я снова скопирую этот обработчик событий и изменю его наsurname. Не забудьте привязать эту функцию. Хорошо, появляется Мэри Поппинс, и мы видим, что программа работает нормально.

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary',
      surname: 'Poppins',
    }
    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleSurnameChange = this.handleSurnameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }

  handleSurnameChange(e) {
    this.setState({
      surname: e.target.value
    })
  }

  render() {
    return (
      <section>
        <Row label="Name">
          <input
            value={this.state.name}
            onChange={this.handleNameChange}
          />
        </Row>
        <Row label="Surname">
          <input
            value={this.state.surname}
            onChange={this.handleSurnameChange}
          />
        </Row>
      </section>
    );
  }

}

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

Концептуально фамилия и имя мало связаны друг с другом. Итак, что нам нужно сделать, это снова позвонитьuseStateловушка для объявления второй переменной состояния. Здесь я объявляю фамилию, конечно, я могу дать ей любое имя, потому что это переменная в моей программе. установить сноваsetSurname. передачаuseState, передавая начальную переменную состояния «Поппинс». Я снова скопировал и вставил этот фрагмент строки. значение изменено наsurname, событие onchange изменяется наhandleSurnameChange. Когда пользователь редактирует фамилию, а не имя господина, мы хотим иметь возможность изменятьsurnameценность .

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
    </section>
  );
}

Давайте посмотрим, работает ли это. Яй, работает нормально. (аплодисменты)

Итак, как мы видим, мы можем использовать несколько хуков в компоненте. Давайте сравним два подхода более подробно. Состояние в знакомом компоненте класса слева всегда представляет собой объект с несколькими полями, который необходимо вызвать.setStateФункция включает некоторые из этих значений в объект состояния. Когда нам нужно его получить, нам нужно позвонитьthis.state.something. В примере с хуком справа мы использовали хук дважды, объявив две переменные: имя и фамилию. и всякий раз, когда мы звонимsetNameилиsetSurname, React получит уведомление о том, что компонент необходимо повторно отобразить, и вызоветsetStateТакой же. Таким образом, в следующий раз, когда React отобразит компонент, он отобразит текущийnameа такжеsurnameпередается компоненту. И мы можем использовать эти переменные состояния напрямую, без вызоваthis.state.something.

Использование контекста React с классами и хуками

OK. Вернемся к нашему примеру компонента класса. Известны ли нам какие-либо другие функции React? Затем еще одна вещь, которую вы, возможно, захотите сделать внутри компонента, — это прочитать контекст. Возможно, вы не знакомы с контекстом, это как глобальная переменная для поддеревьев. Контекст полезен, когда вам нужно получить текущую тему или язык, который использует текущий пользователь. Особенно, когда всем компонентам нужно считывать некоторые из одних и тех же переменных, использование контекста может эффективно избежать постоянной передачи значений через свойства.

давайте импортируемThemeContextа такжеLocaleContext, эти два контекста я определил в другом файле. Вероятно, наиболее знакомым API для использования контекста, особенно множественных контекстов, является API свойства рендеринга. Напишите это так. Я прокручиваю здесь. Мы используем ThemeContext Consumer для получения темы. В моем случае тема представляет собой простой стиль. Я скопировал этот код и поместил его внутрь свойства рендера. БудуclassNameназначить какtheme. Хорошо, очень старый стиль. (смех)

Я также хочу отображать текущий язык, поэтому я собираюсь использовать LocaleContext Consumer. Давайте отобразим еще одну строку, скопируем и вставим сюда эту строку кода и изменим ее на язык.Language. Рендер сюда. Хорошо, мы видим, что контекст работает.

import React from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary',
      surname: 'Poppins',
    }
    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleSurnameChange = this.handleSurnameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }

  handleSurnameChange(e) {
    this.setState({
      surname: e.target.value
    })
  }

  render() {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <section className={theme}>
            <Row label="Name">
              <input
                value={this.state.name}
                onChange={this.handleNameChange}
              />
            </Row>
            <Row label="Surname">
              <input
                value={this.state.surname}
                onChange={this.handleSurnameChange}
              />
            </Row>
            <LocaleContext.Consumer>
              {locale => (
                <Row label="Language">
                  {locale}
                </Row>
              )}
            </LocaleContext.Consumer>
          </section>
        )}
      </ThemeContext.Consumer>

    );
  }

}

Это, вероятно, самая распространенная ситуация контекста потребления. На самом деле мы добавили более удобный API, чтобы получить его на React 16.6. Э, но это ваша обычная многоконтекстная ситуация. Итак, давайте посмотрим, как использовать хуки для достижения той же функциональности.

Как мы уже говорили, состояние — это фундаментальная функция React, поэтому мы можем использоватьuseStateчтобы получить состояние. Поэтому, если мы хотим использовать контекст, нам сначала нужно импортировать мой контекст. импортировать сюдаThemeContextа такжеLocaleContext. Теперь, если я хочу использовать контекст в своем компоненте, я могу использоватьuseContext. можно использоватьThemeContextЧтобы получить текущую тему, используйтеLocaleContextПолучить текущий язык. здесьuseContextОн не только читает контекст, но и подписывается на компонент, и при изменении контекста компонент соответственно обновляется. но сейчасuseContextдаетThemeContextтекущее значение theme , поэтому я могу назначить егоclassName. Затем мы добавляем родственный узел и меняем метку наLanguage, Пучокlocaleположить его здесь. (аплодисменты)

import React, { useState, useContext } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);


  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section className={theme}>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
    </section>
  );
}

Итак, давайте сравним два метода. В примере слева показано, как используется традиционный API поддержки рендеринга. Очень четко показано, что он делает. Но он также содержит немного вложенности, и проблема вложенности возникает не только с контекстом, но и с любым типом API поддержки рендеринга.

Мы также можем добиться той же функциональности, используя хуки. Но код будет более плоским. Итак, давайте посмотрим, мы используем дваuseContext, откуда получаемthemeа такжеlocale. Тогда мы сможем их использовать. Вы можете спросить, откуда React знает, например, здесь я вызываю дваuseState, то как React узнает, какое состояние и какое вызыватьuseStateЭто соответствует? Ответ заключается в том, что React полагается на порядок этих вызовов, что может быть немного необычно.

Чтобы хуки работали правильно, нам нужно следовать правилу при использовании хуков:Невозможно вызвать хук в условном решении, он должен находиться на верхнем уровне вашего компонента. Например, я делаю некоторые суждения, похожие на условие if props, а затем вызываю внутри условияuseStateкрюк. Мы разработали плагин для Линтера, который будет подсказывать «Это неправильный способ использования хуков».

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

Как обрабатывать побочные эффекты, используя как классы, так и хуки

Итак, давайте вернемся и посмотрим на наш класс. Еще одна вещь, которую вы, возможно, захотите сделать с классом, — это функция жизненного цикла. Наиболее распространенный вариант использования функций жизненного цикла — обработка побочных эффектов, таких как отправка запросов или вызов API браузера для отслеживания изменений DOM. Но вы не можете делать эти и подобные вещи на этапе рендеринга, потому что DOM может быть еще не визуализирован в этот момент. Таким образом, способ борьбы с побочными эффектами в React — объявить что-то вродеcomponentDidMountметоды жизненного цикла.

Так, например, позвольте мне показать вам это. Ну, вы видите в верхней части экрана заголовок, отображаемый на вкладке, — React App. На самом деле здесь есть API браузера, который позволяет нам обновлять этот заголовок. Теперь мы хотим, чтобы заголовок этой вкладки был именем человека и мог меняться в зависимости от введенного значения.

Теперь я собираюсь инициализировать его. Что ж, есть браузерный API, который делает это, и этоdocument.title,равныйthis.state.nameдобавить местоthis.state.surname. Теперь мы видим, что здесь изображена Мэри Поппинс. Но если я редактирую имя, заголовок на вкладке не обновляется автоматически, потому что я еще не реализовал его.componentDitUpdateметод. Чтобы этот побочный эффект соответствовал моему рендерингу, я объявляю здесьcomponentDitUpdate, затем скопируйте этот код и вставьте сюда. Теперь заголовок показывает Мэри Поппинс, и если я начну редактировать поле ввода, заголовок вкладки также обновится. Это пример того, как мы можем обрабатывать побочные эффекты в классе.


+  componentDidMount() {
+    document.title = this.state.name + ' ' + this.state.surname;
+  }

+  componentDidUpdate() {
+    document.title = this.state.name + ' ' + this.state.surname;
+ }

Итак, как мы можем использовать хуки для достижения той же функциональности? Способность обрабатывать побочные эффекты — еще одна ключевая особенность компонентов React. Поэтому, если мы хотим использовать побочные эффекты, нам нужно импортировать их из React.useEffect. Затем мы хотим сообщить React после того, как React очистит компонент что делать с ДОМ. Итак, мы передаем функцию в качестве параметра в useEffect, а с побочными эффектами работаем в функции, здесь код меняется наdocument.title = name + ' ' + surname.

-  import React, { useState, useContext } from 'react';
+ import React, { useState, useContext, useEffect } from 'react';

+  useEffect(() => {
+    document.title = name + ' ' + surname;
+  })

Как видите, заголовок страницы гласит «Мэри Поппинс». Если я начну его редактировать, заголовок страницы также обновится.

так,userEffectПо умолчанию он будет выполняться при начальном рендеринге и после каждого обновления. Таким образом, по умолчанию заголовок страницы остается таким же, как отображаемый здесь контент. Вы можете не использовать это поведение по умолчанию из соображений производительности или если у вас есть особая логика. После меня Райан немного коснется этого.

Итак, давайте сравним два метода. В классе слева мы разделяем логику на методы жизненного цикла с разными именами. Вот почему у нас естьcomponentDidMountа такжеcomponentDitUpdate, они срабатывают в разное время. Мы иногда повторяем какую-то логику между ними. Хотя эту логику можно поместить в функцию, нам все равно придется вызывать ее в двух местах и ​​помнить о последовательности.

С перехватчиками эффектов значение по умолчанию является согласованным, и вы можете не использовать это поведение по умолчанию. Следует отметить, что в классе нам нужно получить доступthis.state, поэтому для его реализации требуется специальный API. Но в этом примере с эффектом на самом деле нет необходимости в специальном API для доступа к переменной состояния. Потому что это уже входит в область действия этой функции, объявленной выше. Вот почему эффекты объявляются внутри компонентов. И таким образом у нас также есть доступ к переменным состояния и контексту, и мы можем присваивать им значения.

Две реализации подписки

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

Я помещаю ширину в состояние. использоватьwindow.innerWidthAPI браузера для инициализации. Затем я хочу сделать это. Что ж, давайте скопируем и вставим этот код. Измените его на ширину здесь. Я представлю это здесь. изменить здесьthis.state.width. Это ширина окна, а не ширина Мэри Поппинс. (смеется) Я собираюсь добавить, ну, я собираюсь добавить прослушиватель событий, так что нам нужно на самом деле прослушивать это изменение ширины. Итак, установитеwindow.addEventListener. Я буду слушать событие изменения размера, handleResize. Затем мне нужно объявить это событие. Здесь мы обновляем состояние ширины, установленное наwindow.innerWidth. Затем нам нужно его связать.

Тогда, ну, тогда мне тоже нужно отписаться. Поэтому я не хочу создавать утечки памяти, сохраняя эти подписки. Я хочу отписаться от этого события. То, как мы делаем это в классе, состоит в том, чтобы создать еще один, называемыйcomponentWillUnmountметоды жизненного цикла. Затем я скопировал и вставил сюда этот логический код и изменил его наremoveEventListener. Мы настроили прослушиватель событий и удалили прослушиватель событий. Мы можем проверить, перетащив окно. Вы видите, что эта ширина меняется. Работает нормально.

import React from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default class Greeting extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
        name: 'Mary',
        surname: 'Poppins',
+       width: window.innerWidth,
      }
      this.handleNameChange = this.handleNameChange.bind(this);
      this.handleSurnameChange = this.handleSurnameChange.bind(this);
+     this.handleResize = this.handleResize.bind(this);
  }

    componentDidMount() {
        document.title = this.state.name + ' ' + this.state.surname;
+       window.addEventListener('resize', handleResize);
    }

    componentDidUpdate() {
      document.title = this.state.name + ' ' + this.state.surname;
    }

+   componentWillUnmount() {
+     window.removeEventListener('resize', handleResize);
+   }

+   handleResize() {
+     this.setState({
+       width: window.innerWidth
+     });
+   }

    handleNameChange(e) {
      this.setState({
        name: e.target.value
      })
    }

    handleSurnameChange(e) {
      this.setState({
        surname: e.target.value
      })
    }

  render() {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <section className={theme}>
            <Row label="Name">
              <input
                value={this.state.name}
                onChange={this.handleNameChange}
              />
            </Row>
            <Row label="Surname">
              <input
                value={this.state.surname}
                onChange={this.handleSurnameChange}
              />
            </Row>
            <LocaleContext.Consumer>
              {locale => (
                <Row label="Language">
                  {locale}
                </Row>
              )}
            </LocaleContext.Consumer>
+           <Row label="Width">
+              {this.state.width}
+           </Row>
          </section>
        )}
      </ThemeContext.Consumer>

    );
  }

}

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

вот хочу подписатьсяwindow.addEventListener, изменить размер,handleResize. Затем мне нужно сохранить состояние текущей ширины. Итак, я объявлю еще один набор переменных состояния. Итак, здесь объявляются ширина и setWidth. мы проходимuseStateустановить их начальное значение наwindow.innerWidth. теперь я положилhandleResizeОбъявление функции находится здесь. Потому что больше нигде не называется. затем используйтеsetWidthчтобы установить текущую ширину. Ну, мне нужно сделать это. Поэтому я копирую и вставляю эту строку. Измените его на ширину здесь.

Наконец, мне нужно очистить его после этого эффекта. Поэтому мне нужно указать, как очистить. Концептуально очистка также является частью этого эффекта. Таким образом, этот эффект имеет место для очистки. В этом порядке способ, которым вы можете указать, как очистить подписку, заключается в том, что эффект может дополнительно возвращать функцию. Если он возвращает функцию, React вызовет эту функцию после очистки эффекта. Так что тут отписываемся. Хорошо, давайте проверим, что это работает. Да! (аплодисменты)

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);

  useEffect(() => {
    document.title = name + ' ' + surname;
  })

+ const [width, setWidth] = useState(window.innerWidth);
+ useEffect(() => {
+   const handleResize = () => setWidth(window.innerWidth);
+   window.addEventListener('resize', handleResize);
+   return () => {
+     window.removeEventListener('resize', handleResize);
+   };
+ })

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section className={theme}>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
+     <Row label="Width">
+       {width}
+     </Row>
    </section>
  );
}

Итак, давайте сравним эти два метода. Слева мы используем знакомый компонент класса, ну тут ничего удивительного. У нас есть какие-то побочные эффекты, какая-то родственная логика отделена: мы видим, что здесь задается заголовок документа, но здесь же задается и он. И подписываемся на эффект здесь, извините, здесь подписываемся на событие, а тут отписываемся. Поэтому эти вещи должны быть синхронизированы друг с другом. И этот метод содержит два несвязанных метода в двух несвязанных строках. Так что мне немного сложно тестировать их по отдельности в будущем. Но выглядит очень знакомо, что неплохо.

Тогда этот код может показаться незнакомым. Но давайте посмотрим, что здесь происходит. Что ж, в хуках мы разделяем код не по названию функции жизненного цикла, а по тому, что делает код. Таким образом, мы видим, что это имеет эффект, который мы используем для обновления заголовка документа, что может сделать этот компонент. Вот еще один эффект, который подписывается на событие изменения размера окна, и когда размер окна изменяется, состояние обновляется вместе с ним. Затем, гм, эффект имеет фазу очистки, что и происходит, когда эффект удаляется, React отменяет прослушиватель событий, чтобы избежать утечек памяти. Если вы внимательно следили, то, возможно, заметили, что, поскольку эффект запускается после каждого рендера, мы переподписываемся. Есть способ оптимизировать эту проблему. Значение по умолчанию непротиворечиво, что важно. Если вы, например, используете какой-то реквизит здесь, мне нужно пойти и переподписаться на другой идентификатор из реквизита или что-то в этом роде. Но есть способ оптимизировать его, и вы можете отказаться от такого поведения. Райан расскажет о том, как это сделать, в своем следующем выступлении.

Custom Hook

Хорошо, вот еще одна вещь, которую я хочу продемонстрировать. Теперь, когда компоненты очень большие, это не слишком большая проблема. Учитываем возможность того, что в функциональном компоненте можно делать больше вещей, компонент станет больше, но это вполне нормально. Ну, но вы можете захотеть повторно использовать некоторую логику в других компонентах, или вы хотите извлечь общую логику, или вы хотите протестировать отдельно. Интересно, что вызовы ловушек на самом деле являются вызовами функций. А компоненты — это функции. Итак, как мы обычно разделяем логику между двумя функциями. Мы извлечем общую логику в другую функцию. Это также то, что я буду делать. Я скопировал этот код здесь. Я собираюсь создать новую под названиемuseWindowWidthФункция. Затем вставьте его сюда. Нам нужна ширина внутри компонента, чтобы иметь возможность поместить его оказывать. Потому что мне нужно вернуть текущую ширину внутри этой функции. Затем мы вернемся к приведенному выше коду и изменим его следующим образом:const width = useWindowWidth. (аплодисменты и возгласы)

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
+ const width = useWindowWidth();

  useEffect(() => {
    document.title = name + ' ' + surname;
  })

- const [width, setWidth] = useState(window.innerWidth);
- useEffect(() => {
-   const handleResize = () => setWidth(window.innerWidth);
-   window.addEventListener('resize', handleResize);
-   return () => {
-     window.removeEventListener('resize', handleResize);
-   };
- })

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section className={theme}>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
      <Row label="Width">
        {width}
      </Row>
    </section>
  );
}

+function useWindowWidth() {
+  const [width, setWidth] = useState(window.innerWidth);
+  useEffect(() => {
+    const handleResize = () => setWidth(window.innerWidth);
+    window.addEventListener('resize', handleResize);
+    return () => {
+     window.removeEventListener('resize', handleResize);
+    };
+  })
+  return width;
+}

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

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

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

Здесь переменная ширины дает нам текущую ширину и подписывается на ее обновления. Мы можем пойти еще дальше, если захотим. В этом примере это может быть необязательно, но я хочу дать вам идею. Что ж, возможно, наша возможность установить заголовок документа сложнее, и вы хотите иметь возможность извлекать его логику и тестировать ее отдельно. Поэтому я скопировал и вставил этот код сюда. Я могу написать новый пользовательский хук. Я назвал этот хук какuseDocumentTitle. Так как имя и фамилия не имеют значения в контексте контекста. Я хочу, чтобы заголовок вызывался, заголовок является параметром, и, поскольку пользовательские хуки — это функции JavaScript, они могут передавать параметры, возвращать значение или нет. Здесь я устанавливаю заголовок в качестве параметра. Затем внутри компонента используйтеuseDocumentTitle, параметрnameплюсsurname.


import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
  const width = useWindowWidth();
+ useDocumentTitle(name + ' ' + surname);

- useEffect(() => {
-   document.title = name + ' ' + surname;
- })

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section className={theme}>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
      <Row label="Width">
        {width}
      </Row>
    </section>
  );
}

+function useDocumentTitle(title) {
+  useEffect(() => {
+    document.title = title;
+  })
+}

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  })
  return width;
}

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

Так что, если мы, э, я удалю один из них и извлеку другой. Я собираюсь создать еще один новый крючок, назовите егоuseFormInput. Этот хук — мой обработчик изменений. Теперь я скопирую это заявление сюда. Это определяет состояние поля ввода. здесь уже нетnameа такжеsetName. Я изменил это на что-то более общееvalueа такжеsetValue. Я ставлю начальное значение в качестве параметра. изменить здесьhandleChange, здесь изменено наsetValue. Итак, как мы используем поле ввода в нашем компоненте? Нам нужно получить текущийvalueи изменить обработчик. Это то, что нам нужно присвоить полю ввода. Поэтому мы просто возвращаем их в крючок. Ну, вернисьvalueи onChangehandleChangeфункция. Вернемся к компоненту и изменим его наnameравныйuseFormInput, параметр Мэри. здесьnameстановится объектом, в том числеvalueа такжеonChangeфункция. здесьsurnameравныйuseFormInput, параметр инициализации Poppins. изменить здесьname.valueа такжеsurname.value. Потому что эти два значения и есть нужные нам строки. Затем я удалил это и изменил на атрибут распространения. Кто-то смеется. [Смех] Хорошо. Давайте проверим, что да, он работает нормально.

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
- const [name, setName] = useState('Mary');
- const [surname, setSurname] = useState('Poppins');
+ const name = useFormInput('Mary');
+ const surname = useFormInput('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
  const width = useWindowWidth();
- useDocumentTitle(name+ ' ' + surname);
+ useDocumentTitle(name.value + ' ' + surname.value);

- function handleNameChange(e) {
-   setName(e.target.value);
- }

- function handleSurameChange(e) {
-   setSurname(e.target.value);
- }

  return (
    <section className={theme}>
      <Row label="Name">
-       <input
-         value={name}
-         onChange={handleNameChange}
-       />
+       <input {...name} />
      </Row>
      <Row label="Surname">
-       <input
-         value={surname}
-         onChange={handleSurnameChange}
-       />
+       <input {...surname} />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
      <Row label="Width">
        {width}
      </Row>
    </section>
  );
}

+function useFormInput(initialValue) {
+  const [value, setValue] = useState(initialValue);
+  function handleChange(e) {
+    setValue(e.target.value);
+  }
+  return {
+    value,
+    onChange: handleChange
+  };
+}

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  })
}

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  })
  return width;
}

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

Давайте сравним их в последний раз. Что ж, в знакомом примере компонента класса слева в объекте есть некоторое состояние, некоторые методы связаны, и некоторая логика разбросана по разным методам цикла объявления, и эта логика представляет собой ряд функций обработки событий. Что ж, мы использовали контент из контекста для рендеринга контента. Что ж, эта ситуация нам вполне знакома.

2018-11-21 1 32 40

На правой панели он отличается от наших общих компонентов React. Но это имеет смысл. Даже если вы не знаете, как реализованы эти функции. Как видите, эта функция используется для организации поля ввода, эта функция использует контекст для получения темы и местного языка, эта функция использует ширину окна и заголовок документа, а затем отображает ряд содержимого. Если мы хотим узнать больше, мы можем прокрутить окно вниз, и вы увидите, что это код того, как работает поле ввода, вот код того, как установить заголовок документа, и вот код того, как установить и подписаться на ширину окна. Возможно, это пакет npm, вам не нужно знать, как он реализован. Мы можем вызывать его внутри компонентов или копировать и вставлять их между компонентами. Hook предоставляет настраиваемые хуки, которые дают пользователям возможность создавать свои собственные абстрактные функции.Пользовательские хуки не сделают ваше дерево сборки React огромным и помогут избежать «пакетного ада». (аплодисменты)

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

2018-11-21 1 33 37

Крюк предложение

Вернемся к слайдам. Хорошо, это тот слайд, о котором вы можете твитнуть. (смех)

2018-11-18 10 24 17

Сегодня мы покажем вам предложение Hook. Хуки позволяют нам использовать многие функции React без использования классов. И мы не отказываемся от классов, но мы даем вам новую возможность не писать классы. Мы намерены завершить все варианты использования хуков вместо классов как можно скорее. Не хватает части, но мы над этим работаем. А хуки позволяют вам повторно использовать логику с отслеживанием состояния, извлекать ее из компонентов, тестировать их отдельно, повторно использовать между компонентами и избегать введения «адской упаковки».

Важно отметить, что хуки не являются критическим изменением, полностью обратно совместимы и строго аддитивны. Вы можете найти нашу документацию по хукам по этому адресу. Что ж, мы будем рады услышать ваши отзывы, а сообщество React будет радо услышать ваши мысли о хуках, нравится вам это или нет. И мы обнаружили, что трудно получить встречное мнение, не позволяя людям использовать крючки. Итак, мы выпустили хук-билд для React 16.7 alpha. Это не основной релиз, это второстепенный релиз. Но в этой альфа-версии можно попробовать использовать хуки. И мы тестировали его в продакшене на Facebook в течение месяца, так что мы не думаем, что будут серьезные ошибки. Но API хука можно настроить на основе ваших отзывов. И я не рекомендую вам переписывать все ваше приложение, используя хуки. Потому что, во-первых, крючки все еще находятся в стадии предложения. Вторая причина, я лично считаю, что образ мышления об использовании хуков требует изменения мышления.Возможно, вы будете сбиты с толку, когда попытаетесь сначала преобразовать компоненты класса в хуки. Но я рекомендую всем попробовать использовать хуки в новом коде и сообщить нам, что вы об этом думаете. Ну, спасибо всем. (аплодисменты)

2018-11-18 10 43 11

По нашему мнению, хуки представляют собой будущее React. Но я думаю, что это также представляет собой то, как мы продвигаем React вперед. То есть мы не делаем больших переписываний. Что ж, мы надеемся, что новые паттерны, которые нам больше нравятся, смогут сосуществовать со старыми, чтобы мы могли сделать постепенную миграцию и принять эти новые паттерны, так же, как вы постепенно приняли сам React.

Крюк был всегда

Это также почти конец моего выступления. Но, наконец, я хотел бы поделиться некоторыми из моих личных взглядов. Я изучаю React четыре года назад. Первый вопрос, который у меня возник, заключался в том, зачем использовать JSX.

Что ж, мой второй вопрос заключается в том, что именно означает логотип React. Проект React не имеет названия «Атом», это не физический движок. Ну, одно из объяснений заключается в том, что React основан на реакциях, а атомы тоже участвуют в химических реакциях, поэтому в логотипе React используется изображение атомов.

2018-11-18 10 12 48

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

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

У меня тоже такое же отношение к HOOK. Я чувствую, что крючок не является новой функцией. Я чувствую, что Hook предоставляет возможность использовать известные нам функции React, такие как состояние, контекст и жизненные циклы. И я чувствую, что этот хук более интуитивно понятен, как React. Хук действительно объясняет, как компоненты работают внутри компонента. Я чувствую, что Крюк был скрыт от наших глаз в течение четырех лет. На самом деле, если вы посмотрите на логотип React, вы увидите электронную дорожку, и кажется, что Хук там был. Благодарю. (аплодисменты)


портал

Если вы обнаружите ошибки в переводах и субтитрах или в других областях, требующих доработки, добро пожаловать вРепозиторий GitHubИзмените и прорекламируйте английские субтитры или переводы, спасибо. А потом это видео и третий абзац, который Райан принес мне позже, называется90% Cleaner React with Hooks, и заинтересованные друзья могут принять участие в корректуре и переводе английских субтитров.