В чем разница между функциональными компонентами React и классами React?
Ранее стандартным ответом было сказать, что классы предоставляют больше функций (например, состояние). имеютHooks, это не так.
Вероятно, вы слышали, что один из них работает лучше. который из? Существует множество таких тестов производительности.дефект, поэтому я буду осторожен сполучить заключение. Производительность в основном зависит от кода, а не от выбора функции или класса. По нашим наблюдениям, даже если стратегия оптимизацииразные, но разница в производительности на самом деле очень мала.
С другой стороны, мыНе рекомендуетсяПерепишите написанные вами компоненты, если только у вас нет других причин и вы не возражаете против того, чтобы стать первым экспериментатором. Хуки все еще очень новы (например, React в 2014 году), и некоторые «лучшие практики» еще не записаны в руководствах.
Есть ли существенная разница между функциями и классами React? Конечно, они -- в ментальной модели.В этой статье я рассмотрю самые большие различия между ними.. Это в функциональных компонентах в 2015 годупредставил, но это часто упускается из виду:
Функциональный компонент фиксирует значение после рендеринга.
Давайте проанализируем, что это значит.
Примечание. В этой статье не рассматриваются классы или функции, я только описываю разницу между двумя моделями программирования в React. Дополнительные сведения об использовании функций см.Часто задаваемые вопросы по хукам.
Рассмотрим этот компонент:
function ProfilePage(props) {
const showMessage = () => {
alert('Followed ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Follow</button>
);
}
Он показывает фиктивный сетевой запрос сsetTimeout
И тогда кнопка появится во всплывающем окне подтверждения. Например, еслиprops.user
для'Dan'
, он появится через три секунды'Followed Dan'
,очень простой.
(Обратите внимание, что в приведенном выше примере, независимо от того, использую ли я стрелки или обычные функции,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, и если вы собираетесь чаще использовать функции в приложениях React, возможно, вам захочется это понять.
Мы проиллюстрируем разницу с помощью ошибки, которая обычно встречается в приложениях React..
Используйте селектор текущего элемента и два предыдущихProfilePage
реализовать, чтобы открыть этопример песочницы- Одна кнопка «Подписаться» на каждый рендер.
Используйте две кнопки в этой последовательности операций:
- нажмитеодна из кнопок.
- через 3 секундыИзменятьВыберите запись.
- посмотриТекст, который всплывает.
Вы заметите особую разницу:
-
так какfunctionиз
ProfilePage
, нажмите «Подписаться на запись Дэна», а затем переключитесь на запись Софи, все еще всплывает'Followed Dan'
. -
так какclassиз
ProfilePage
, он появится'Followed Sophie'
:
В этом примере первое поведение является правильным.Если вы подписаны на одного человека, а затем переключитесь на запись другого человека, мой компонент не должен путаться в том, за кем я следую. Реализация класса явно является ошибкой.
Так почему же наш пример класса ведет себя именно так?
Давайте поближе познакомимся с классомshowMessage
метод:
class ProfilePage extends React.Component {
showMessage = () => {
alert('Followed ' + this.props.user);
};
Этот метод класса читаетthis.props.user
PROPS не является переменным в React.но,this
да, и изменилось.
Собственно, это то, что есть в классеthis
Для целей самого React он со временем мутирует, так что вы получаете новые версии по мере рендеринга и жизни.
Поэтому, если наш компонент повторно отображается в состоянии запроса,this.props
изменится.showMessage
метод от "слишком новый"props
получено вuser
.
Это раскрывает интересное явление в природе слоя пользовательского интерфейса. Если мы говорим, что UI концептуально является функцией текущего состояния приложения,тогда обработчик события является частью визуализируемого результата - точно так же, как визуальный вывод. Наш обработчик событий «принадлежит» конкретному рендеру с определенными реквизитами и состоянием.
Однако планирование резервного чтенияthis.props
Тайм-аут разорвет соединение. нашshowMessage
Обратный вызов не «привязан» к какому-либо конкретному рендеру, поэтому он «теряет» правильные реквизиты и вместо этого читаетthis
Прервите эту связь.
Можно сказать, что функциональный компонент не имеет этой проблемы.. Как мы собираемся решить эту проблему?
Мы хотим как-то «починить» правильный реквизитrender
и получить ихshowMessage
Связи между обратными вызовами. по этому путиprops
будет потерян.
Один из способов — читать в начале событияthis.props
, а затем явно передать их обработчику времени ожидания:
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
вызываются другие методы, и этот метод читаетсяthis.props.something
илиthis.state.something
, у нас снова будет та же проблема. Так что мы должныthis.props
а такжеthis.state
передается в качестве параметра каждому вызовуshowMessage
Методы.
Это обычно нарушает эргономику, обычно предоставляемую классом, а также его трудно запомнить или применить, поэтому у людей часто возникают ошибки.
Аналогично, положитьalert
положить вhandleClick
не может решить эту проблему. Мы хотим структурировать код таким образом, чтобы можно было разделить больше методов, а также считывать соответствующие реквизиты и состояние рендеринга, связанные с этим вызовом.Эта проблема даже не уникальна для реагирования - вы можете поставить данные во что угодноthis
Воспроизведите его в библиотеке пользовательского интерфейса для изменяемых объектов..
Возможно, мы можем в конструктореbindметод?
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, могут быть решены.
Замыканий обычно избегают, потому что это оченьтрудныйЗнайте значения, которые могут меняться со временем. Но в React свойства и состояние неизменяемы! (Или, по крайней мере, это настоятельная рекомендация.) Это устраняет убийственную особенность замыканий.
Это означает, что если вы заблокируете свойства или состояние определенного рендеринга, вы всегда можете получить то же самое:
class ProfilePage extends React.Component {
render() {
// 捕获 props!
const props = this.props;
// 注意: 我们在 *render 里面*
// 这不是 class 方法。
const showMessage = () => {
alert('Followed ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return <button onClick={handleClick}>Follow</button>;
}
}
У вас есть «захваченные» реквизиты во время рендеринга.:
Таким образом, любой код внутри него (включаяshowMessage
) гарантированно увидят пропсы для этого конкретного рендера, и React больше не будет «трогать наш сыр».
Мы можем добавить столько вспомогательных методов, сколько захотим, и все они используют захваченные свойства и состояние., что спасло закрытие.
пример вышеНичего страшного, но выглядит странно. Если вы определяете функцию в рендере вместо использования метода класса, что еще делает класс?
На самом деле мы можем упростить код, убрав класс «shell»:
function ProfilePage(props) {
const showMessage = () => {
alert('Followed ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Follow</button>
);
}
Как указано выше,props
Все же поймал — React передает их как аргументы.В отличие отthis
,props
Сам объект не изменяется React.
Если деструктуризация во время определения функцииprops
Это более очевидно:
function ProfilePage({ user }) {
const showMessage = () => {
alert('Followed ' + user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Follow</button>
);
}
Когда родительский компонент рендерится с разными реквизитамиProfilePage
, React позвонит сноваProfilePage
метод. Но программа событий, которую мы нажали на «принадлежит», имеет свою собственнуюuser
значение предыдущего рендера и прочитать егоshowMessage
Обратный звонок, они все целы.
Вот почему, вэто демоВ функциональной версии переключение на Сунила после нажатия кнопки «Подписаться» во всплывающем окне «Софи».'Followed Sophie'
:
Этот ответ правильный.(Хотя вы также можетеСледуйте за Сунилом! )
Теперь мы понимаем самую большую разницу между функциями и классами в React:
Функциональный компонент фиксирует значение после рендеринга.
С хуками тот же принцип применяется к состоянию. Рассмотрим этот пример:
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>
</>
);
}
([Этоонлайн демо. ])
Хотя это не хорошая новость Application Ui, который реализует то же самое: если я отправлю конкретное сообщение, этот компонент не должен быть запутанным, какое сообщение для отправки. Эта функция компонентов消息
Захватывает состояние и «принадлежит» возвращает рендеринг, вызванный событием щелчка браузера. так это消息
устанавливается на значение на входе, когда я нажимаю «отправить».
Итак, мы знаем, что функции в React захватывают свойства и состояние по умолчанию.но если мынадеятьсяЧитайте последние реквизиты или состояние, они не относятся к конкретному рендеру, как это сделать? Если мы хотимчитать их в будущемКак сделать?
На уроках можно читатьthis.props
илиthis.state
,потому чтоthis
сам по себе изменчив, React изменит его. В функциональном компоненте вы также можете иметь изменяемое значение, совместно используемое всеми рендерингами компонента, называемое «ref»:
function MyComponent() {
const ref = useRef(null);
// 你可以读写 `ref.current`。
// ...
}
Тем не менее, вы должны управлять им самостоятельно.
поля ref и instanceиграть ту же роль, который является убежищем в мире переменных команд. Вы можете быть знакомы с «рефами DOM», но принцип гораздо более банален, это просто коробка, в которую вы можете положить вещи.
Даже визуально,this.someting
выглядит как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
, мы получим последнее значение, даже если продолжаем печатать после нажатия кнопки отправки.
вы можете сравнить этодва demosУвидеть разницу. ref — это способ «отказаться» от согласованности рендеринга, что может быть удобно в некоторых случаях.
В общем, вам следует избегать рендерингапериодПрочитайте или установите ссылки, поскольку они изменяемы. Мы хотим, чтобы рендеринг оставался предсказуемым.Однако, если мы хотим получить последнее значение определенного реквизита или состояния, обновление ссылки вручную будет громоздким.. Мы можем автоматизировать его с помощью эффектов:
function MessageThread() {
const [message, setMessage] = useState('');
// 保持 track 是最新值
const latestMessage = useRef('');
useEffect(() => {
latestMessage.current = message;
});
const showMessage = () => {
alert('You said: ' + latestMessage.current);
};
(Этоdemo. )
Мы в действиивНазначение для достижения значений ref меняется только при обновлении DOM. Это гарантирует, что наша мутация не нарушит такие вещи, какРазделение времени и саспенси другие функции, которые прерывают рендеринг.
refs редко используются таким образом,Захват свойств или состояния лучше по умолчанию. Однако при работе с такими вещами, как таймеры и подпискихитрые APIвремя очень удобное. Помните, что вы можете отслеживать любое такое значение — реквизиты, переменные состояния, весь объект реквизита или даже функцию.
Этот паттерн также можно использовать для оптимизации — например, вuseCallback
Когда индикатор часто меняется. но,использовать редукторОбычно одинлучшее решение. (Об этом будет написано в следующем блоге!)
В этой статье мы увидели общий шаблон поломки в классах и то, как замыкания могут помочь нам исправить это. Однако вы могли заметить, что при попытке оптимизировать хуки, указав массив зависимостей, вы можете столкнуться с ошибками с устаревшими замыканиями. Означает ли это, что проблема заключается в закрытии? Я тоже так не думаю.
Как мы видели раньше, замыкания действительно помогают нам.ремонттонкие проблемы, которые трудно заметить. Точно так же они позволяют писать впараллельный режимКод ниже для правильной работы становится проще. Это может быть связано с тем, что логика внутри компонента блокирует правильные свойства и состояние после рендеринга.
Во всех случаях, замеченных до сих пор,Проблема «устаревшего замыкания» возникает из-за ложных предположений о том, что «функции не меняются» или «реквизит всегда один и тот же».. Это не так, надеюсь, эта статья поможет прояснить ситуацию.
Функции блокируют свои реквизиты и состояние — важно только то, что они собой представляют. Это не баг, а особенность функционального компонента. Например, функции не должны начинаться сuserEffect
илиuseCallback
исключается из «массива зависимостей». (Обычно используемые соответствующие исправления, упомянутые выше, либоuseReducer
илиuseRef
решение - мы скоро объясним, как выбирать между ними в документации)
Поскольку мы пишем большую часть кода React с функциями, нам необходимо адаптировать нашоптимизировать кода такжекакое значение будет менятьсяСлучай.
Лучшее умственное правило, которое я нашел до сих пор с хуками, это «любое значение кода, кажется, меняется в любое время».
Функции не исключение. Потребуется некоторое время, чтобы это стало обычным явлением в учебных материалах по React, и потребуется некоторое время, чтобы привыкнуть к менталитету класса, но я надеюсь, что эта статья поможет вам увидеть это в новом свете.
Функции React всегда фиксируют свои значения — и теперь мы знаем, почему.
Это совершенно разные покемоны.
перевести оригиналHow Are Function Components Different from Classes?(2019-03-03)