Для будущих студентов курса "React.js Developer" подготовили перевод материала.

Также предлагаем всем желающим посмотреть открытый вебинар на тему «ReactJS: быстрый старт. Сильные и слабые стороны».


Написание четкого и удобочитаемого кода необходимо для улучшения его качества. Кроме того, чистый код легче тестировать. Нет причин не потратить пять лишних минут на рефакторинг кода, чтобы сделать его более читаемым.

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

  1. Используйте event.target.name для обработчиков событий.

  2. Как избежать ручной привязки обработчиков событий к this?

  3. Используйте React hooks для обновления состояния.

  4. Кэширование затратных операций с useMemo.

  5. Разделение функций на отдельные элементы для улучшения качества кода.

  6. Как создавать собственные хуки в React?

#1: Используйте имя обработчика события

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

Однако, пишете ли Вы десять обработчиков событий, когда у Вас форма с десятью полями ввода? Ответ — нет.

Мы можем установить свойство имени на поле ввода и получить доступ к нему из обработчика события. Это значение позволяет нам использовать один обработчик входных данных для событий onChange.

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

export default class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            item1: "",
            item2: "",
            items: "",
            errorMsg: ""
        };
        this.onFirstInputChange = this.onFirstInputChange.bind(this);
        this.onSecondInputChange = this.onSecondInputChange.bind(this);
    }
    onFirstInputChange(event) {
        const value = event.target.value;
        this.setState({
            item1: value
        });
    }
    onSecondInputChange(event) {
        const value = event.target.value;
        this.setState({
            item2: value
        });
    }
    render() {
        return (
            <div>
                <div className="input-section">
                    {this.state.errorMsg && (
                        <p className="error-msg">{this.state.errorMsg}</p>
                    )}
                    <input
                        type="text"
                        name="item1"
                        placeholder="Enter text"
                        value={this.state.item1}
                        onChange={this.onFirstInputChange}
                    />
                    <input
                        type="text"
                        name="item2"
                        placeholder="Enter more text"
                        value={this.state.item2}
                        onChange={this.onSecondInputChange}
                    />
                </div>
            </div>
      );
    }
}

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

Давайте рассмотрим эту ситуацию по-другому, используя поле имени. Мы можем получить доступ к этому значению через свойство event.target.name. Теперь создадим одну функцию, которая сможет обрабатывать оба события одновременно. Таким образом, мы можем удалить обе функции onFirstInputChange и onSecondInputChange.

onInputChange = (event) => {
  const name = event.target.name;
  const value = event.target.value;
  this.setState({
    [name]: value
  });
};

Полегче, да? Конечно, вам часто требуется дополнительная проверка данных, которые вы сохраняете в своем состоянии (state). Вы можете использовать утверждение switch для добавления собственных правил валидации для каждого введенного значения.

#2: Избегайте ручной привязки (binding) this

Скорее всего, вы знаете, что React не сохраняет привязку this при прикреплении обработчика  к событию onClick или onChange.  Поэтому мы должны связать это вручную. Почему мы связываем *this*? Мы хотим связать this обработчика события (event handler) с экземпляром компонента (component’ instance), чтобы не потерять его контекст, когда мы передадим его в качестве обратного вызова.

Вот классический пример «привязки», который происходит в конструкторе.

class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicked: false };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.props.setState({ clicked: true });
  }
  
  render() {
    return <button onClick={this.handleClick}>Click me!</button>;
  );
}

Однако привязка больше не требуется, так как команда CLI createe-react-app использует @babel/babel-plugin-transform-class-properties плагин версии >=7 и babel/plugin-proposal class-properties плагин версии <7.

Примечание: Вы должны изменить синтаксис обработчика событий (event handler) на синтаксис функции стрелок (arrow function).

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

class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicked: false };
  }
  
  handleClick = () => this.setState({clicked: true });
  render() {
    return <button onClick={this.handleClick}>Click me!</button>;
  );
}

Это так просто! Вам не нужно беспокоиться о привязке функций в вашем конструкторе.

#3: Используйте React hooks чтобы обновить ваше состояние

Начиная с версии 16.8.0, теперь можно использовать методы состояния и жизненного цикла (state and lifecycle methods) внутри функциональных компонентов с помощью React Hooks. Другими словами, мы можем писать более читаемый код, который также намного проще в обслуживании.

Для этого мы будем использовать useState hook. Для тех, кто не знает, что такое hook и зачем его использовать — вот краткое определение из React documentation..

Что такое Hook? Hook — это специальная функция, которая позволяет «подключиться» к функциям React. Например, useState — это Hook, который позволяет добавлять состояние React к функциональным компонентам.

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

Во-первых, давайте посмотрим, как мы обновляем состояние с помощью setState hook.

this.setState({
    errorMsg: "",
    items: [item1, item2]
});

А теперь давайте воспользуемся useState hook. Нам нужно импортировать этот hook из react библиотеки.  Теперь мы можем объявить новые переменные состояния и передать им начальное значение. Мы будем использовать деструктуризацию, чтобы извлечь переменную для получения значения и еще одну для установки значения (это будет функция). Рассмотрим, как это можно сделать в приведенном выше примере.

import React, { useState } from "react";

const App = () => {
  const [items, setIems] = useState([]);
  const [errorMsg, setErrorMsg] = useState("");
};

export default App;

Теперь вы можете получить прямой доступ как к константам items, так и к errorMsg в вашем компоненте.

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

import React, { useState } from "react";

const App = () => {
  const [items, setIems] = useState([]);
  const [errorMsg, setErrorMsg] = useState("");

  return (
    <form>
        <button onClick={() => setItems(["item A", "item B"])}>
          Set items
        </button>
    </form>
  );
};

export default App;

Вот как ты можешь использовать state hooks (хуки состояния).

#4: Кэширование затратных операций с помощью useMemo

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

Поэтому мы можем использовать hook useMemo для запоминания вывода при передаче тех же параметров в мемоизуемую функцию (memoized function). Hook useMemo принимает функцию и вводимые параметры для запоминания. React ссылается на это как на массив зависимостей. Каждое из значений, упоминаемое внутри функции, также должно появиться в массиве зависимостей.

Вот простой, абстрактный пример. Мы передаем два параметра a и b затратной функции. Так как функция использует оба параметра, то мы должны добавить их в массив зависимостей для нашего useMemo hook.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

#5: Разделение функций на чистые функции (Pure functions, PF) для улучшения качества кода

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

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

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

Функциональное программирование — это процесс создания программного обеспечения путем компоновки чистых функций, избегая общего для всех состояния, изменчивых (mutable) данных и побочных эффектов. Чистые функции более читабельны и легче поддаются тестированию. Поэтому они улучшают качество кода.

- freeCodeCamp

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

function ascSort (a, b) {
  return a < b ? -1 : (b > a ? 1 : 0);
}

function descSort (a, b) {
  return b < a ? -1 : (a > b ? 1 : 0);
}

#6: Создайте собственные React Hooks 

Мы научились использовать useState и useMemo React hooks. Тем не менее, React позволяет вам устанавливать свои собственные React hooks, чтобы сформировать необходимую логику и делать компоненты более читабельными.

Мы можем задать пользовательские React hooks, начиная с ключевого слова use, как и все другие React hooks. Это выгодно, когда вы хотите распределить логику между различными функциями. Вместо копирования функции, мы можем определить логику как React hook и повторно использовать ее в других функциях.

Вот пример React-компонента, который обновляет состояние, когда размер экрана становится меньше 600 пикселей. Если это происходит, переменная isScreenSmall устанавливается в true. В противном случае переменная устанавливается в false. Мы используем событие resize из объекта окна для обнаружения изменения размера экрана.

const LayoutComponent = () => {
  const [onSmallScreen, setOnSmallScreen] = useState(false);

  useEffect(() => {
    checkScreenSize();
    window.addEventListener("resize", checkScreenSize);
  }, []);

  let checkScreenSize = () => {
    setOnSmallScreen(window.innerWidth < 768);
  };

  return (
    <div className={`${onSmallScreen ? "small" : "large"}`}>
      <h1>Hello World!</h1>
    </div>
  );
};

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

import { useState, useEffect } from "react";

const useSize = () => {
  const [isScreenSmall, setIsScreenSmall] = useState(false);

  let checkScreenSize = () => {
    setIsScreenSmall(window.innerWidth < 600);
  };
  useEffect(() => {
    checkScreenSize();
    window.addEventListener("resize", checkScreenSize);

    return () => window.removeEventListener("resize", checkScreenSize);
  }, []);

  return isScreenSmall;
};

export default useSize;

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

В этом примере мы назвали пользовательский React hook useSize. Теперь мы можем импортировать useSize hook в любом месте, где нам это необходимо.

React custom hooks — в этом нет ничего нового. Вы обертываете логику с функцией и даете ей имя, которое начинается с use. Она действует как обычная функция. Однако, следуя правилам "use", вы говорите всем, кто её импортирует, что это hook (хук). Более того, поскольку это hook, вы должны структурировать его так, чтобы следовать rules of hooks (правилам хуков).

Вот как сейчас выглядит наш компонент. Код становится намного чище!

import React from 'react'
import useSize from './useSize.js'

const LayoutComponent = () => {
  const onSmallScreen = useSize();

  return (
    <div className={`${onSmallScreen ? "small" : "large"}`}>
      <h1>Hello World!</h1>
    </div>
  );
}

Бонусный совет: Мониторинг фронтенда

Мониторинг вашего фронтенда является обязательным, если вы хотите взять на себя ответственность за то, что ваши пользователи делают и как ваше приложение реагирует на их запросы. Asayer — это инструмент мониторинга фронтенда, который отображает все, что делают пользователи, и показывает, как веб-приложение ведет себя при возникновении любой проблемы. Он позволяет воспроизводить проблемы, объединять ошибки JS и контролировать производительность вашего веб-приложения.

Вот и все! Эта статья научила вас шести методикам улучшения читабельности и качества кода.


Узнать подробнее о курсе "React.js Developer".

Посмотреть открытый вебинар на тему «ReactJS: быстрый старт. Сильные и слабые стороны».