Жёсткая структура 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-приложениях.
Если подход и библиотека вам понравились, поставьте репозиторию звезду на гитхабе. Надеюсь этот подход будет вам полезен.
