Рефакторинг через классы: вычищаем свой JavaScript-код

Обложка поста

Перевод статьи «‎JavaScript code cleanup: how you can refactor to use Classes»

Иван Капцов

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

Обычный рефакторинг

В обычном рефакторинге вы берёте функцию для компонента и выносите её в другой файл.

Из:

const MyComponent = () => {
 const someFunction = () => 'Hey, I am text'
 return (
   <div>
     {someFunction()}
   </div>>
 )
}

В:

import { someFunction } from 'functionHelper.js'
const MyComponent = () => {
 return (
   <div>
     {someFunction()}
   </div>
 )
}

И

export const someFunction = () => 'Hey, I am text'

Это простой пример, но суть подхода ясна:

  1. Вынести функции в отдельный файл.
  2. Импортировать их в другой и вызывать как обычно.

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

Рефакторинг через классы

Для того, чтобы понять этот подход,  посмотрите на репозиторий, созданный специально для этого материала.

Фрагмент демо

Первоначальный коммит выполняет всю функциональность внутри основного компонента (App.js), а последующие коммиты рефакторят код для использования классов.

Вы можете запустить демо сами. Не забудьте выполнить yarn install.

Мы начнём с компонента, который «получает» объект (имитируя способ, которым мы могли бы сделать это через API) с определёнными атрибутами: repeat (количество блоков), side (height and width), text, color. У нас есть несколько способов управления видом — изменение цвета, обновление текста и так далее. После каждого изменения мы показываем сообщение.

Например, вот метод изменения ширины и высоты:

changeSide = side => {
  const obj = {...this.state.obj, side}
  this.fetchObject(obj);
  this.setState({ message: `You changed the sides to ${side} pixels!` });
}

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

Вместо передачи большого числа параметров мы можем использовать класс с конструктором:

export default class ObjectManipulator {
  constructor( { object, fetchObject, markResettable, updateMessage, updateStateValue } ) {
    this.fetchObject = fetchObject;
    this.markResettable = markResettable;
    this.updateMessage = updateMessage;
    this.updateStateValue = updateStateValue;
  }

  changeSide = ( object, side ) => {
    const newObject = { ...object, side };
    this.fetchObject(newObject);
    this.updateMessage(`You changed the sides to ${side} pixels!`);
    this.markResettable();
    this.updateStateValue('side', side);
  };
};

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

const manipulator = new ObjectManipulator({
  object,
  fetchObject: this.fetchObject,
  markResettable: this.markResettable,
  updateMessage: this.updateMessage,
  updateStateValue: this.updateStateValue,
});

Здесь создаётся объект manipulator — экземпляр нашего класса ObjectManipulator. Когда мы вызываем manipulator.changeSide(object, '800'), он запускает метод changeSide, который мы определили выше. Нет необходимости передавать updateMessage или любой другой метод — мы уже передали их в конструкторе.

Этот способ становится полезным, если мы используем много методов. В нашем случае нужно было вызывать .then (res => myFunction (res) после всего, что пытались извлечь. Теперь не нужно писать лишний код благодаря определению в экземпляре класса.

Держать всё на своих местах

Следующий приём организации кода может быть очень полезен, чтобы держать всё на своих местах. Например, есть константный массив цветов, к которым применяется функция map(), чтобы получились цветные кнопки, которые вы видите в демо. Перемещая эту константу в ObjectManipulator, можно убедиться, что она не конфликтует с другими переменными colors в приложении:

export default class ObjectManipulator {
  [...]

  colors = ['blue', 'red', 'orange', 'aquamarine', 'green', 'gray', 'magenta'];
};

Таким образом можно использовать manipulator.colors, чтобы получить правильные цвета для страницы, сохранив глобальную константу colors для чего-то другого.

В итоге мы почистили код, сделали его понятней и читабельней.

Не смешно? А здесь смешно: @ithumor