По мере того, как React рос и развивался на протяжении многих лет, мы наблюдали изменения в известной среде Javascript/Typescript с точки зрения лучших практик создания и реализации компонентов. Как человек, который учился с компонентами на основе классов (и рассказывал о них другим), переписывание компонентов с использованием синтаксиса функциональных компонентов имело свои особенности. В этом посте будет описано и продемонстрировано, как вы можете взять ваши текущие компоненты класса и преобразовать их в функциональные компоненты с помощью нескольких быстрых переписываний.
Базовый состав компонентов с использованием методов жизненного цикла
В следующих примерах у нас есть оболочка макета компонента на основе классов с компонентами на основе функций.
Если вы не знакомы с компонентом на основе классов, вот основы:
- Прежде чем компонент будет смонтирован в DOM, запускается метод
constructor()
. Затем компонент выполняет начальный проход методаrender()
. - После первого запуска
render()
в жизненном цикле вызываетсяcomponentDidMount()
, который является необязательным методом, и запускает там любой код. После завершенияcomponentDidMount()
снова запускаетсяrender()
, аcomponentDidMount()
больше никогда не вызывается. - Если происходит обновление состояния и оно определено, каждый раз запускается
componentDidUpdate()
, за которым следует еще один запуск методаrender()
. - Наконец, когда компонент собирается быть размонтированным из дерева DOM, вызывается
componentWillUnmount()
для выполнения любой работы по очистке, если вы ее реализовали.
По сути, порядок операций компонента на основе классов — constructor() ---> render() ---> componentDidMount() ---> render() ---> componentDidUpdate <---> render() ---> componentWillUnmount()
. Функциональные компоненты могут быть написаны с похожим жизненным циклом, но с большим отличием; каждый из этих методов жизненного цикла может быть обработан хуком useEffect()
! Если мы возьмем наш пример на основе классов, вот как он потенциально может выглядеть как функциональный компонент:
Для функциональных компонентов useEffect()
является универсальным крючком. В приведенном выше примере мы видим различные способы написания useEffect()
для достижения паритета с методами жизненного цикла компонентов на основе классов. useEffect()
— это хук, который, вообще говоря, принимает в качестве параметра анонимную функцию. Когда конкретный useEffect()
вызывается в жизненном цикле, данный код анонимной функции запускается соответствующим образом. По своей сути, без каких-либо дополнений или модификаций, useEffect(() => { ... })
работает как componentDidUpdate()
.
componentDidMount()
Написано как функция Hook
Чтобы преобразовать его в поведение componentDidMount()
, нам нужно добавить пустые скобки []
в качестве аргумента к крючку, выделенного жирным шрифтом, как показано: useEffect(() => { ... }, [])
. Вы спросите, а зачем добавлять []
? Причина в том, что передача пустого списка в useEffect()
говорит джинну обработчика жизненного цикла запускать этот хук до тех пор, пока заданные значения состояния в []
не меняются. Поскольку мы не добавляем никаких значений состояния в список в []
, таких как [someFunState]
, useEffect(() => { ... }, [])
запустится только один раз, потому что нет значений состояния, за которыми следует эта реализация, которые могли бы повторно запустить ее.
componentDidUpdate()
с хуком условной записи как функции
Если вы когда-либо писали componentDidUpdate()
, скорее всего, вы написали по крайней мере один с каким-то условием состояния, чтобы вы случайно не запускали части кода в своей реализации. useEffect()
устраняет эту проблему, потому что вы можете отделить свой условный код, когда запускается обновление, просто используя несколько перехватчиков useEffect()
! Предположим, у нас есть два значения состояния: stateA и stateB. С компонентом на основе классов, чтобы гарантировать выполнение кода при обновлении только одного из состояний, мы должны написать что-то вроде:
componentDidUpdate(previousState) { if (previousState.stateA !== this.state.stateA) { // ... conditional code when stateA updates } if (previousState.stateB !== this.state.stateB) { // ... conditional code when stateB updates } // ... other code that runs when either state updates }
Вместо этого мы могли бы сделать следующее:
// code that runs whenever there is any state update useEffect(() => { ... })
useEffect(() => {
// ... code run when stateA updates}, [stateA])
useEffect(() => {
// ... code run when stateB updates}, [stateB])
Здесь у нас есть универсальный useEffect()
, который будет запускать некоторый код, очень похожий на componentDidUpdate()
, без каких-либо условий. Затем во втором и третьем хуках useEffect()
каждый из них запускается тогда и только тогда, когда изменяется соответствующее значение состояния, переданное в параметре списка (например, [stateA]
). Это означает, что вместо того, чтобы componentDidUpdate()
перебирать все возможности, мы можем иметь хук useEffect()
, посвященный определенной переменной состояния, когда она была обновлена. Это не только делает наш компонент более модульным с внутренней работой обновлений изменения состояния, но и предлагает оптимизацию производительности, потому что вы можете пропустить useEffect()
, когда данная переменная состояния не изменяется.
componentWillUnmount()
Написано как функциональный хук
В отличие от других методов жизненного цикла в компоненте на основе классов, настройка componentWillUnmount()
в случае функционального компонента не так проста. Вместо этого вы должны вернуть какую-то функцию очистки как часть соответствующего метода useEffect()
. Например, если ваш код очистки должен быть запущен после того, как ваш componentDidMount()
сделал определенные вызовы службы или подписки на API, тогда ваш хук useEffect()
будет иметь двойную цель: выполнить реализацию componentDidMount()
и реализацию componentWillUnmount()
.
useEffect(() => { // ... code you want ran during a componentDidMount() return () => { // ... code that will be run once the component unmounts // from the DOM tree } }, [])
Выше мы пишем код, который мы обычно делаем, и добавляем []
, чтобы показать, что этот хук useEffect()
будет запущен только один раз. Кроме того, этот хук возвращает функцию. Возвращенная функция действует как наша componentWillUnmount()
и будет выполняться, как только компонент будет размонтирован из дерева DOM.
Использование компонента
Одна из прелестей использования функциональных компонентов вместо компонентов на основе классов заключается в том, что то, как вы объявляете их в других компонентах, абсолютно не меняется! В приведенном ниже примере демонстрируется взаимодействие основанных на классах и функциональных компонентов:
import { ClassComponentExample } from './ClassComponentExample' import { FunctionComponentExample } from './FunctionComponentExample' import { Component } from 'react' function App() { const onClick = () => {} const componentProps = { id: "componentId", onClick: onClick, text: "This is a component, but what kind?" } return( <ClassComponentExample {...componentProps}/> <FunctionComponentExample {...componentProps}/> ) }
Использование реквизита
В отличие от методов жизненного цикла, доступ к свойствам, которые передаются в ваших компонентах, имеет только одно ключевое отличие. Для компонентов на основе классов вам может быть знакомо ключевое слово this
, прежде чем вы получите доступ ко многим переменным и функциям. С функциональными компонентами мы опускаем this
(каламбур)! В следующем примере мы показываем наш «Пример использования компонента» и то, как каждый из этих примеров реализован и использует props
:
Чтобы использовать props
в ClassComponentExample
, мы набираем this.props.propName
. В FunctionComponentExample
мы просто вводим props.propName
без ключевого слова this
. Например:
// class component // ... Notice bold text for this keyword return( <div> <div> {this.props.text} Well, it has {this.props.id}</div> <button onClick={this.props.onClick}>Click me!</button> </div> ) //function component // ... Notice how this is not used at all return( <div> <div> {props.text} Well, it has {props.id}</div> <button onClick={props.onClick}>Click me!</button> </div> )
Объявление и использование состояний
Как и в случае с нашим товарищем props
, мы следуем аналогичному соглашению о доступе. В компонентах на основе классов мы вводим this.state.stateVariableName
для доступа к нему. Вместо этого с функциональными компонентами мы напрямую обращаемся к переменной состояния, такой как stateVariableName
. Но как? Это связано с тем, что декларация штатов существенно отличается. В следующем примере показано, как реализовать одинаковые состояния между классовым и функциональным компонентом.
В ClassComponentExample
мы устанавливаем состояние в конструкторе с некоторыми начальными значениями, используя:
this.state = { id: "componentId", text: "This is a component, but what kind?" }
Вместо этого в FunctionComponentExample
мы создаем наши состояния, используя кортеж в формате const [<stateVariableName>, <setStateVariableName>] = useState(<initial value>)
. Переменная, объявленная в первом месте, является переменной состояния. Это переменная только для чтения, которую мы не можем изменить. Во втором месте мы объявляем имя дескриптора для изменения значения состояния. В довершение всего, мы сообщаем React, что const [<stateVariableName>, <setStateVariableName>]
— это кортеж состояния, вызывая хук функции useState()
, которому затем присваивается начальное значение переменной состояния. Таким образом, чтобы преобразовать пример на основе классов в функциональный компонент, мы должны переписать наше объявление состояния следующим образом:
const [text, setText] = useState("This is a component, but what kind?") const [id, setId] = useState("componentId")
Наконец, чтобы изменить и обновить значения состояния, в следующих примерах показано, как это делается:
// class-based component this.setState({ text: "new text value" }) //functional component setText("new text value")
Каждый пример работает точно так же в соответствующем контексте. В компоненте на основе классов мы обычно вызываем this.setState({})
и сообщаем React, какие состояния мы хотим обновить. Вместо этого в наших функциональных компонентах мы вызываем каждый отдельный дескриптор состояния, который хотим обновить, в данном случае это setText()
.
Краткое содержание
К этому моменту я надеюсь, что эта статья помогла вам описать основные различия между классовыми и функциональными компонентами. Независимо от того, конвертируете ли вы свои компоненты на основе классов в функциональные компоненты или возвращаетесь обратно, я искренне надеюсь, что это помогло вам. Если вы обнаружите какие-либо ошибки или у вас возникнут вопросы, не стесняйтесь оставлять комментарии. Я люблю учиться у других так же, как я могу поделиться и научить! :)