[Перевод] Разница между функциональными компонентами React и компонентами класса

React.js

[Перевод] Разница между функциональными компонентами React и компонентами класса

оригинальный:слишком остро отреагировал.IO/как-женщины…

существуетReact.jsВ разработке,функциональный компонент(функциональный компонент) икомпонент классаКакая разница?

В прошлом часто считалось, что разница заключается в том, что компоненты класса предоставляют больше возможностей (таких какstate). вместе сReact HooksПриход , это утверждение не выполняется (через хуки функциональные компоненты также могут иметьstateи обратные вызовы жизненного цикла класса).

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

По любой из вышеперечисленных причин мы всене предлагаетсяВы переопределяете существующие компоненты класса функциональными компонентами, если только у вас нет других причин или вы не любите есть первым.React HooksВсе еще очень новый (как React в 2014 году), и в настоящее время нет лучших практик использования хуков.

Кроме вышеперечисленного, есть ли еще отличия? Действительно ли существуют фундаментальные различия между функциональными компонентами и классовыми компонентами?«Конечно, есть — в ментальной модели»(правда не знаю как это выразить, пожалуйста, выкладывайте оригинальный текст автора 😅)В этой статье мы рассмотрим самые большие различия между этими двумя типами компонентов.. Эта разница, в 2015 году функциональные компоненты функциональные компонентыЗнакомство с реакциейС тех пор он существует, но большинство людей игнорирует его.

Различия в функциональных компонентах и ​​компонентах класса

Функциональный компонент фиксирует состояние внутри рендера.

Давайте шаг за шагом рассмотрим, что это значит.

**Обратите внимание, что эта статья не является оценочным суждением о функциональных компонентах и ​​компонентах классов. Я просто показываю разницу между этими двумя моделями компонентного программирования в экосистеме React. Чтобы узнать, как лучше использовать функциональные компоненты, рекомендуется официальная документацияHooks FAQ** .

Предположим, у нас есть следующий функциональный компонент:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

Компонент отображает кнопку.При нажатии кнопки он имитирует асинхронный запрос и отображает всплывающее окно в функции обратного вызова запроса. Например, еслиprops.userЗначениеDanА затем нажмите кнопку на 3 секунды, мы увидимFollowed DanЭто всплывающее окно. очень простой.

(Обратите внимание, что в приведенном выше коде нет разницы между использованием стрелочных функций и обычных функций.thisвопрос. Замените стрелочные функции обычными функциямиfunction handleClick()Нет проблем )

Как мы можем реализовать компонент класса, который делает то же самое? Очень простой перевод:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

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

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

**Если вы хотите увидеть исходный код онлайн, вы можетекликните сюда**. Остальная часть этой статьи посвящена объяснению этой разницы и ее важности.

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

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

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

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

Для каждой кнопки подписки выполните следующие действия:

  1. Нажмите на 1 из следующих кнопок
  2. В течение 3 секунд повторно выберите имя раскрывающегося списка.
  3. Через три секунды обратите внимание на текст предупреждения о разнице во всплывающем окне.

Вы должны заметить разницу между двумя всплывающими окнами предупреждений:

  • В контрольном примере функционального компонента выберите раскрывающийся списокDan, нажмите кнопку «Подписаться», чтобы быстро переключить раскрывающийся список наSophie, через 3 секунды содержимое всплывающего окна оповещения по-прежнемуFollowed Dan
  • В случае тестирования компонента класса повторите то же действие, через 3 секунды появится всплывающее окно с предупреждением.Followed Sophie

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

(PS, я также рекомендую вам действительно обратить вниманиеSophie)

Так почему же возникают проблемы с компонентами нашего класса?

Давайте подробнее рассмотрим компонент классаshowMessageвыполнить:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

Этот метод будет читатьthis.props.user. В экосистеме Reactpropsэто неизменяемые данные, которые никогда не меняются.но,thisно всегда переменная.

В самом деле,thisСмысл существования изменчив. В процессе выполнения реакции он изменитthisданные, чтобы убедиться, что вы можетеrenderИ другие методы жизненного цикла, чтение последних данных (реквизиты, состояние).

Поэтому, если наш компонент перерендерится во время обработки сетевого запроса,this.propsизмененный. после этого,showMessageМетод будет читать измененноеthis.props.

Это раскрывает интересный факт о рендеринге пользовательского интерфейса. Если мы думаем о пользовательском интерфейсе (UI) как о визуальном представлении текущего состояния приложения (UI=render(state)),Затем обработчик событий также является частью результата рендеринга, как и пользовательский интерфейс.. Наша функция обработчика событий относится к моменту срабатывания события.renderи связанный рендерpropsа такжеstate.

Однако мы используем таймер (setTimeout) для задержки вызова в обработчике события нажатия кнопки.showMessage, НарушениеshowMessageа такжеthis.propsассоциация.showMessageОбратный вызов больше не привязан ни к какому рендеру, и исходные связанные реквизиты также теряются. отthisПрочтите данные о том, разорвав эту ассоциацию.

Если функциональный компонент не существует, так как мы решим эту проблему?

Нам нужно как-то это исправитьshowMessageи это принадлежитrenderИ объединение соответствующих реквизитов.

С одной стороны, мы можем прочитать текущие реквизиты в обработчике нажатия кнопки, а затем явно передать их вshowMessage, как показано ниже:

class ProfilePage extends React.Component {
  showMessage = (user) => {
    alert('Followed ' + user);
  };

  handleClick = () => {
    const {user} = this.props;
    setTimeout(() => this.showMessage(user), 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

Сюдаможет решить эту проблему. Однако это решение позволяет нам вводить избыточный код, который со временем может привести к возникновению других проблем. если нашshowMessageКак насчет методов, чтобы прочитать больше реквизита? еслиshowMessageА как насчет состояния доступа?еслиshowMessageвызывается другой метод, и этот метод считывает другое состояние, напримерthis.props.somethingилиthis.state.something, мы снова столкнемся с той же проблемой. нам может понадобитьсяshowMessageявный проходthis.props this.stateк другим вызываемым методам.

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

Опять же, просто поместите весь код вonClickВ функции-обработчике это принесет нам другие проблемы. Из соображений удобочитаемости кода, удобства сопровождения и т. д. мы обычно разбиваем большие функции на несколько независимых небольших функций.Эта проблема не только в реакции, все вthisЛегко встретить эту проблему в библиотеках класса UI, которые поддерживают переменные данные на.

Может быть, мы можем связать какие-то методы в конструкторе класса?

class ProfilePage extends React.Component {
  constructor(props) {
    super(props);
    this.showMessage = this.showMessage.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  showMessage() {
    alert('Followed ' + this.props.user);
  }

  handleClick() {
    setTimeout(this.showMessage, 3000);
  }

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

К сожалению, приведенный выше кодне можемрешить эту проблему! Помните, причина этой проблемы в том, что мы читаемthis.propsВремя слишком позднее и не имеет ничего общего с грамматикой.Однако, если мы можем полностью полагаться на механизм замыкания JavaScript, то эту проблему можно полностью решить..

Чаще всего мы пытаемсяизбегатьИспользуйте замыкания, потому что в случае замыканий оценка значения переменной переменной станетнекоторые трудности. Однако в реакции свойства и состояниенеизменный(Строго говоря, мы настоятельно рекомендуем props и state как неизменяемые данные). Неизменная природа props и state отлично решает проблему использования замыканий.

Это означает, что если вrenderметод, через закрытие доступа к свойствам и состоянию, мы можем гарантировать, что вshowMessageПри выполнении реквизиты и состояние, к которым осуществляется доступ, являются данными при выполнении рендеринга:

class ProfilePage extends React.Component {
  render() {
    // Capture the props!
    const props = this.props;

    // Note: we are *inside render*.
    // These aren't class methods.
    const showMessage = () => {
      alert('Followed ' + props.user);
    };

    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };

    return <button onClick={handleClick}>Follow</button>;
  }
}

Когда рендер выполняется, вы успешно захватываете реквизит в это время..

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

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

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

Действительно, мы сбрасываем слой класса, и остается функциональный компонент:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

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

Если в параметрах функции поставить структуру props, то код будет выглядеть понятнее:

function ProfilePage({ user }) {
  const showMessage = () => {
    alert('Followed ' + user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

Когда родительский компонент передается вразныереквизит для повторного рендерингаProfilePage, ответ вызовет сноваProfilePage. Но перед этим щелкаем обработчик события кнопки Follow, который захватил реквизит последнего рендера.

Вот почему, вэтот примерв функциональном компоненте проблем нет.

Как видите, функция полностью правильная. (PS еще раз, предлагаю и вам обратить на это вниманиеSunil)

Теперь мы понимаем эту разницу между функциональными компонентами и компонентами класса:

Функциональный компонент фиксирует состояние внутри рендера.

Функциональные компоненты с React Hooks

ТамHooksВ случае функциональный компонент также будет захватывать внутреннюю визуализацию.state. Взгляните на этот пример:

function MessageThread() {
  const [message, setMessage] = useState('');

  const showMessage = () => {
    alert('You said: ' + message);
  };

  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = (e) => {
    setMessage(e.target.value);
  };

  return (
    <>
      <input value={message} onChange={handleMessageChange} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

(онлайн-демонстрация,кликните сюда)

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

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

В компоненте класса нам просто нужно просто прочитатьthis.props this.stateДоступ к последним данным, потому что реагирование будет изменятьthis. В функции сборки мы также можем иметьпеременные данные, он может использовать одни и те же данные в каждом рендере. это на крючкахuseRef:

function MyComponent() {
  const ref = useRef(null);
  // You can read or write `ref.current`.
  // ...
}

Тем не менее, вы должны поддерживать его самостоятельноrefсоответствующее значение.

в функциональном компонентеrefи в компоненте классасвойства экземпляраигралта же роль. вы, возможно, уже знакомы сDOM refs, но в крючкахrefболее общий. в крючкахrefПросто контейнер, вы можете положить в контейнер все, что хотите.

Это даже похоже на то, что внутри компонента классаthis.somethingТоже на крючкахsomething.currentПодобные, они представляют одну и ту же концепцию.

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

function MessageThread() {
  const [message, setMessage] = useState('');
  const latestMessage = useRef('');

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = (e) => {
    setMessage(e.target.value);
    latestMessage.current = e.target.value;
  };

если мы былиshowMessageчитать вmessageполе, то мы получим значение поля ввода, когда мы нажмем кнопку. Однако если мы прочитаемlatestMessage.current, мы получим последнее значение в поле ввода — даже после того, как мы нажмем кнопку отправки, мы продолжим вводить новый контент.

вы можете сравнитьЭти две демонстрацииДавайте посмотрим разницу.

В общем, вам следует избегать чтения или изменения ссылок в функции рендеринга, потому что ссылки изменяемы. Мы хотим, чтобы результаты рендеринга были предсказуемы.Однако, если мы хотим получить последнее значение реквизита или состояния, было бы утомительно вручную обновлять значение refs каждый раз.. В этом случае мы можем использоватьuseEffectэтот крючок:

function MessageThread() {
  const [message, setMessage] = useState('');

  // Keep track of the latest value.
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

(demo это здесь)

мы вuseEffectдля обновления значения ref, что гарантирует, что ref будет обновляться только после обновления DOM. Это гарантирует, что наши изменения в ref не нарушат некоторые новые функции в реакции, такие какРазделение времени и прерывание, которые все полагаются на прерываемый рендеринг.

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

Использование ref также применимо в некоторых сценариях оптимизации производительности. такие как использованиеuseCallbackВремя. но,использовать useReducerВ большинстве сценариевлучшее решение.

Суммировать

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

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

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

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

так какчто сказал Фредрик:

Самое важное правило, которое я усвоил при использовании хуков реакции, это «любая переменная может быть изменена в любое время».

Функции также подчиняются этому правилу.

реагирующие функции всегда фиксируют реквизиты, состояние, и, наконец, вновь подчеркнул.

Аннотация: Местами не понял как перевести, есть опущения, рекомендуется читать исходный текст!