Простая фабрика — довольно простой и распространенный паттерн проектирования, подробнее о котором, и не только, уже рассказано в этой статье.
Суть данной статьи в раскрытии одной из возможностей typescript, которым мало кто пользуется, на примере компонента «фабрики».
Абстрактная задача
Создать компонент(далее «компонент-фабрика»), который, в зависимости от принимаемых им параметров, будет возвращать подходящий к ним компонент(далее «подкомпонент»). При этом учтем то, что:
-
Компонент не должен принимать таких параметров, при которых он ломается
-
Должна быть возможность расширять компонент, а именно добавлять новый подкомпонент
-
Он должен выполнять только те вычисления, которые соответвствуют возращаемому подкомпоненту
-
При использовании компонента должно быть понятно какие параметры он принимает для каждого подкомпонента и что возращает в ответ
Реальная задача
Создать компонент, который, по переданным ему параметрам, рисует подходящую фигуру.
Моделирование
Смоделируем фигуры и параметры, необходимые для их отрисовки:
-
Прямоугольник — высота и ширина
-
Круг — радиус
-
Треугольник — три стороны
У каждой фигуры уникальные параметры, но в добавок они могут иметь общий параметр — имя класса (css).

Компонент-фабрика, которую мы создадим, сможет принимать параметры всех фигур, но, чтобы он вернул нам подходящую фигуру, мы будем указывать в параметрах тип фигуры.
Как это выглядит на Javascript
Создадим абстрактные подкомпоненты (фигуры), т. к. нас в текущем контексте не интересует их реализация.
const Rectangle = ({className, width, height}) => ...; const Circle = ({className, radius}) => ...; const Triangle = ({className, sideA, sideB, sideC}) => ...;
Переходим к компоненту фабрике.
const Figure = ({ className, type, width, height, radius, sideA, sideB, sideC, }) => { switch (type) { case "rectangle": return height && width ? ( <Rectangle className={className} height={height} width={width} /> ) : null; case "circle": return radius ? <Circle className={className} radius={radius} /> : null; case "triangle": return sideA && sideB && sideC ? ( <Triangle className={className} sideA={sideA} sideB={sideB} sideC={sideC} /> ) : null; default: return null; } };
Кратко описание того, что в нем происходит.
По переданному параметру type, определяем какая это фигура. Проверяем приходят ли все необходимые для этой фигуры параметры, если да, рисуем фигуру.
Проблемы, которые возникают при использовании javascript
-
Разработчик, используя наш компонент-фабрику, не знает какие параметры ему передать, чтобы получить нужную ему фигуру. Ему придется лазить в нашем коде, чтобы понять что к чему.
-
Если параметры фигуры приходят из предка пятого поколения или же, к примеру, из redux store, велик шанс, что параметры станут когда-то некорректны и придется потратить уйму времени, чтобы понять, почему не отрисовалась фигура.
-
Если передать все параметры одновременно, то фабрика отрисует фигуру, переданную в type, используя подходящие ему параметры, а остальные параметры будут проигнорированы. Это прибавит неясности в логике нашего компонента. Пример:

Использование компонента-фабрики со всеми пропсами одновременно -
Если передать некорректные параметры, узнать об этом можно только просмотрев отрисовванный результат в браузере, так как IDE нам никак не поможет.
Как же это выглядит на typescript
Для начала определим параметры, которые будут иметь все фигуры. Это имя класса, как мы уже писали в главе «Моделирование».
interface IBaseFigure { className?: string; }
Создадим те же абстрактные подкомпоненты, что и в варианте с javascript, но теперь с типизацией.
interface ICircle extends IBaseFigure { radius: number; } const Circle: React.VFC<ICircle> = ({className, radius}) => ...;
interface IRectangle extends IBaseFigure { width: number; height: number; } const Rectangle: React.VFC<IRectangle> = ({className, width, height}) => ...;
interface ITriangle extends IBaseFigure { sideA: number; sideB: number; sideC: number; } const Triangle: React.VFC<ITriangle> = ({className, sideA, sideB, sideC}) => ...; );
Обьяснение того, что написано выше
Мы создали интерфейс BaseFigure, а потом расширяем его интерфейсами наших фигур с помощью «extend». Если мы определим новый параметр в BaseFigure, то он появится во всех интерфейсах расширяюших его.
В интерфейсах мы также указали какого типа должны быть параметры, className должен быть строкой и является необязательным, а остальные параметры обязательны и должны быть числом.
Сами подкомпоненты мы протипизировали с помощью встроенного в React типа VFC, который означает «Функциональный компонент без children».
В целом такого «определения» будет достаточно в рамках этой статьи.
Протипизируем также нашу фабрику.
interface IFigureRectangle extends IRectangle { type: "rectangle"; } interface IFigureCircle extends ICircle { type: "circle"; } interface IFigureTriangle extends ITriangle { type: "triangle"; } type IFigure = IFigureRectangle | IFigureCircle | IFigureTriangle; const Figure: React.VFC<IFigure> = (props) => { switch (props.type) { case "rectangle": { return <Rectangle {...props} />; } case "circle": { return <Circle {...props} />; } case "triangle": { return <Triangle {...props} />; } default: return null; } };
Сначала расширяем интерфейсы фигур, добавляя к ним свойство type с определенным значением. Если type задать такое значение, то остальные параметры компонента определятся по этому интерфейсу.
На пальцах
Мы объединяем(строка 13) такие интерфейсы и получаем единый интерфейс, где в случае, если параметр type равно:
rectangle, остальныe параметры будут из IRectangle
circle, остальные параметры будут из ICircle
triangle, остальные параметры будут из ITriangle
Теперь, благодаря типизации, компонент-фабрика решила проблемы, которые были в варианте с использованием js. Также у нас отпала необходимость проверять приходят ли все параметры.
Мы можем внутри «case»-ов узнавать, какие параметры будут получены.


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

Но так, как type обязательный параметр, то его обязательно нужно будет прописать. И как только мы это сделаем, подсказка будет показывать только подходящие параметры и typescript начнет ругаться при использовании неподходящих.


Возможно на скриншотах не совсем наглядно, но что есть, то есть. Оставляю ссылку на codesanbox, где вы можете потыкать и в живую посмотреть результат.
Вывод
Пользуясь typescript, мы получаем такие плюсы, как:
-
Избавление от лишних проверок на наличие параметров
-
Подсказки во время разработки самого компонента-фабрики
-
Подскази во время его использования
-
Предупреждение от IDE, если передавать в него некорректные параметры
На больших и долгоживущих проектах это важные преимущества, которыми нужно пользоваться.
ссылка на оригинал статьи https://habr.com/ru/post/650719/

Добавить комментарий