Перевод статьи «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'
Это простой пример, но суть подхода ясна:
- Вынести функции в отдельный файл.
- Импортировать их в другой и вызывать как обычно.
Когда программа становится сложнее, вам приходится передавать много разных параметров — объекты, функции для управления состоянием и так далее. В итоге можно столкнуться с проблемой, когда нужно извлечь из компонента три функции, каждая из которых требует одинаковых входных данных (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
для чего-то другого.
В итоге мы почистили код, сделали его понятней и читабельней.