Жёсткая структура React-компонентов
При работе над React-приложениями я часто сталкиваюсь с тем, что мои коллеги смешивают в одном файле и JSX, и CSS-in-JS стили, и логику, и типы компонента. Работать с таким месивом очень сложно. Даже если настаивать на выделении логики, стилей и типов в отдельные файлы, это то делается, то нет. Для введения жёсткой структуры компонентов мною была написана простейшая библиотека react-component-structure.
https://github.com/sergeyshpadyrev/react-component-structure
Работает она простейшим образом. Любой компонент необходимо разделить на три хука и файл с типами:
-| Component
-| index.ts
-| logic.ts
-| render.tsx
-| style.ts
-| types.ts
В файле logic.ts мы пишем хук useLogic - контроллер компонента, включающий в себя всю его бизнес-логику - все хуки useCallback, useEffect, useMemo и подобные. В этом хуке у нас есть доступ к props компонента.
import { useCallback, useState } from 'react';
import type { Props } from './types';
const useLogic = (props: Props) => {
const [count, setCount] = useState(props.defaultCount);
const onClickMinus = useCallback(() => setCount((c) => c - 1), []);
const onClickPlus = useCallback(() => setCount((c) => c + 1), []);
return {
count,
onClickMinus,
onClickPlus,
};
};
export default useLogic;
В файле styles.ts мы помещаем хук useStyle со стилями нашего компонента. Тут мы можем использовать inline-стили, CSS-in-JS или Tailwind. В этом хуке у нас есть доступ к props нашего компонента и к его контроллеру.
import type { Props } from './types';
import useLogic from './logic';
import { useMemo } from 'react';
const useStyle = (props: Props, logic: ReturnType<typeof useLogic>) =>
useMemo(
() => ({
counter: {
fontSize: logic.count + 10,
},
title: {
color: props.color,
},
}),
[logic.count, props.color],
);
export default useStyle;
В файле render.tsx мы помещаем хук useRender с JSX, то бишь отображение компонента. В этом хуке у нас есть доступ и к props компонента, и к его контроллеру logic, и к стилям.
import type { Props } from './types';
import type useLogic from './logic';
import type useStyle from './style';
const useRender = (props: Props, logic: ReturnType<typeof useLogic>, style: ReturnType<typeof useStyle>) => {
return (
<div>
<div style={style.title}>Hello {props.greeting}!</div>
<div style={style.counter}>Count: {logic.count}</div>
<div onClick={logic.onClickMinus}>Decrease</div>
<div onClick={logic.onClickPlus}>Increase</div>
</div>
);
};
export default useRender;
В index.ts файле мы соединяем все три хука с помощью функции createComponent:
import { createComponent } from 'react-component-structure';
import useLogic from './logic';
import useRender from './render';
import useStyle from './style';
const Component = createComponent({ useLogic, useRender, useStyle });
export default Component;
И в файле types.ts мы объявляем тип для props компонента:
export interface Props {
color: string;
defaultCount: number;
greeting: string;
}
Если у компонента нет props, то можно объявить их так:
export type Props = unknown
При использовании каждый компонент нашего приложения имеет чёткую структуру, состоящую из файлов контроллера, отображения, стилей и типов. Это разделение подобно разделению на HTML (отображение), CSS (стили) и JavaScript (контроллер) в ванильных JS-приложениях.
Если подход и библиотека вам понравились, поставьте репозиторию звезду на гитхабе. Надеюсь этот подход будет вам полезен.