Основы реакции

Компоненты функции React и хуки

Функции React, такие как функции состояния и жизненного цикла, доступны только для компонентов на основе классов.

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

Но с момента выпуска React Hooks функциональные компоненты стали первоклассными гражданами React. Это позволило функциональным компонентам создавать, повторно использовать и совместно использовать код React новыми способами.

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

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

React функциональные компоненты

React позволяет вам писать компоненты, используя синтаксис JavaScript class и function. В прошлом написание компонентов с использованием синтаксиса class требовалось, если вы хотите, чтобы ваши компоненты использовали функции React, такие как state и функции жизненного цикла.

Но в феврале 2019 года команда React выпустила функцию под названием хуки, которая позволяет вам писать полнофункциональные компоненты React, используя только функции JavaScript.

В части 1 этой книги вы видели, как можно писать компоненты, используя синтаксис как class, так и function:

import React from 'react';
class MyClassComponent extends React.Component {
  render(){
    return <h1> Hello World </h1>
  }
}
function MyFunctionComponent(){
  return <h1> Hello World </h1>
}

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

function MyFunctionComponent(props){
  return <h1> Hello World </h1>
}

Чтобы завершить введение, вот как компонент класса вызывает компонент функции:

import React from 'react';
class MyClassComponent extends React.Component {
  render(){
    return <MyFunctionComponent name="John"/>
  }
}
function MyFunctionComponent(props){
  return <h1> Hello World {props.name}</h1>
}

Далее вы узнаете о хуках React и о том, как они позволяют функциональным компонентам создавать, повторно использовать и делиться кодом React удобным способом.

Хук useState

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

React предоставляет вам 10 функциональных хуков, но только 2 из них будут часто использоваться при написании функциональных компонентов. Это крючки useState и useEffect. Давайте сначала узнаем о useState.

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

import React, { useState } from 'react'
function UserComponent() {
  const [name, setName] = useState('John')
}

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

Это означает, что у нас есть состояние с именем name, и мы можем обновить его, вызвав функцию setName(). Давайте используем его в операторе return:

import React, { useState } from 'react'
function UserComponent() {
  const [name, setName] = useState('John')
  return <h1> Hello World! My name is {name} </h1>
}

Поскольку функциональные компоненты не имеют функции setState(), для ее обновления необходимо использовать функцию setName(). Вот как изменить имя с «Джон» на «Люк»:

import React, { useState } from 'react'
function UserComponent() {
  const [name, setName] = useState('John')
  if(name === "John"){
    setName("Luke")
  }
  return <h1> Hello World! My name is {name} </h1>
}

Когда у вас есть несколько состояний, вы можете вызывать хук useState столько раз, сколько вам нужно. Хук получает все допустимые типы данных JavaScript, такие как строка, число, логическое значение, массив и объект:

import React, { useState } from 'react'
function UserComponent() {
  const [name, setName] = useState('Jack')
  const [age, setAge] = useState(10)
  const [isLegal, setLegal] = useState(false)
  const [friends, setFriends] = useState(["John", "Luke"])
  return <h1> Hello World! My name is {name} </h1>
}

И это все, что нужно сделать. Хук useState в основном позволяет функциональным компонентам иметь собственное внутреннее состояние.

Хук useEffect

Хук useEffect представляет собой комбинацию методов жизненного цикла классов componentDidMount, componentDidUpdate и componentWillUnmount. Этот хук — идеальное место для настройки слушателя, извлечения данных из API и удаления слушателей до того, как компонент будет удален из DOM.

Давайте рассмотрим пример useEffect в сравнении с методами жизненного цикла класса. Обычно в компоненте класса мы пишем такой код:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Nathan',
    };
  }
  componentDidMount() {
    console.log(
      `didMount triggered: Hello I'm ${this.state.name}`
    );
  }
  componentDidUpdate() {
    console.log(
      `didUpdate triggered: Hello I'm ${this.state.name}`
    );
  }
  render() {
    return (
      <div>
        <p>{`Hello I'm ${this.state.name}`}</p>
        <button
          onClick={() =>
            this.setState({ name: 'Gary'})
          }
        >
          Change me
        </button>
      </div>
    );
  }
}

Поскольку componentDidMount запускается только один раз, когда компонент вставляется в древовидную структуру DOM, последующий рендеринг больше не будет запускать этот метод. Чтобы запустить что-то на каждом рендере, вам нужно использовать метод componentDidUpdate.

Использование хука useEffect похоже на наличие componentDidMount и componentDidUpdate в одном методе, поскольку useEffect запускается при каждом рендеринге. Он принимает два аргумента:

  • (обязательно) Функция для запуска при каждом рендере
  • (необязательно) Массив переменных состояния для отслеживания изменений. useEffect будет пропущен, если ни одна из переменных не будет обновлена.

Переписывание вышеуказанного класса в функциональный компонент будет выглядеть так:

const Example = props => {
  const [name, setName] = useState('Nathan');
  useEffect(() => {
    console.log(`Hello I'm ${name}`);
  });
  return (
    <div>
      <p>{`Hello I'm ${name}`}</p>
      <button
        onClick={() => {
          setName('Gary')
          }}>
        Change me
      </button>
    </div>
  )
}

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

useEffect(() => {
    console.log(`Hello I'm ${name} and I'm a ${role}`);
  }, 
  [name]);

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

Эффект componentWillUnmount

Если у вас есть код, который нужно запустить, когда компонент будет удален из дерева DOM, вам нужно указать метод componentWillUnmount, написав оператор return в первом аргументе функции. Вот пример:

useEffect(() => {
    console.log(`useEffect function`);
    return () => { console.log("componentWillUnmount effect"); }
  }, [name] );

Запуск useEffect только один раз

Чтобы запустить хук useEffect только один раз, как функцию componentDidMount, вы можете передать пустой массив во второй аргумент:

useEffect(
  () => {
    console.log(`useEffect function`);
  }, 
  [] );

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

Правила крючков

При использовании хуков нужно следовать этим двум правилам:

Вызывать хуки только на верхнем уровне

Не вызывайте хуки внутри циклов, условий и вложенных функций. Если вы хотите использовать некоторые хуки условно, напишите условие внутри этих хуков.

Не делайте этого:

if (name !== '') {
 useEffect(function logger() {
    console.log(name)
 });
}

Вместо этого сделайте следующее:

useEffect(function logger() {
  if (name !== '') {
    console.log(name)
  }
});

Это правило гарантирует, что хуки вызываются в одном и том же порядке каждый раз при рендеринге компонента. Именно это позволяет React правильно сохранять состояние хуков между несколькими вызовами useState и useEffect.

Вызывайте хуки только из функциональных компонентов

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

Условный рендеринг с React

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

import React from "react"
function App(props) {
  const {user} = props
  if (user) {
    return <button>Logout</button>
  }
  return <button>Login</button>
}
export default App

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

Частичный рендеринг с обычной переменной

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

import React from "react"
function App(props) {
  const {user} = props
  let button = <button>Login</button>
  if (user) {
    button = <button>Logout</button>
  }
  return (
    <>
      <h1>Hello there!</h1>
      {button}
    </>
  )
}
export default App

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

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

Встроенный рендеринг с оператором &&

Возможен рендеринг компонента только при выполнении определенного условия и рендеринг null в противном случае. Например, предположим, вы хотите отображать динамическое сообщение для пользователей, когда у них есть новые электронные письма в папке «Входящие»:

import React from "react"
function App() {
  const newEmails = 2
  return (
    <>
      <h1>Hello there!</h1>
      {newEmails > 0 &&
        <h2>
          You have {newEmails} new emails in your inbox.
        </h2>
      }
    </>
  )
}
export default App

Встроенный рендеринг с условным (тернарным) оператором

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

import React from "react"
function App(props) {
  const {user} = props
  return (
    <>
      <h1>Hello there!</h1>
      { user? <button>Logout</button> : <button>Login</button> }
    </>
  )
}
export default App

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