docs: добавить примеры React
- добавлен раздел примеров React в сайдбар - добавлены примеры создания и композиции фабрик - перенесены подробные React-примеры из раздела модулей - обновлены сгенерированные артефакты документации
This commit is contained in:
@@ -11,6 +11,14 @@ const sidebar = [
|
|||||||
{ text: 'Монорепозитории', link: '/architecture/monorepo' },
|
{ text: 'Монорепозитории', link: '/architecture/monorepo' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Примеры React',
|
||||||
|
items: [
|
||||||
|
{ text: 'Создание фабрики', link: '/examples/react/factory' },
|
||||||
|
{ text: 'Композиция фабрик', link: '/examples/react/factory-composition' },
|
||||||
|
{ text: 'Композиция через Provider', link: '/examples/react/composition-provider' },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
@@ -194,87 +194,11 @@ Business-модуль всегда экспортирует фабрику. Фа
|
|||||||
|
|
||||||
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
||||||
|
|
||||||
### Структура business-модуля
|
### Примеры
|
||||||
|
|
||||||
```text
|
Пример реализации фабрики в React см. в [Создание фабрики](/examples/react/factory).
|
||||||
business/customer/
|
|
||||||
├── customer.factory.ts
|
|
||||||
├── index.ts
|
|
||||||
└── types/
|
|
||||||
├── customer.type.ts
|
|
||||||
├── customer-api.type.ts
|
|
||||||
├── customer-deps.type.ts
|
|
||||||
└── customer-factory.type.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Типы
|
Пример композиции фабрик в React screen-модуле см. в [Композиция фабрик](/examples/react/factory-composition).
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/customer/types/customer-api.type.ts
|
|
||||||
export type CustomerApi = {
|
|
||||||
useCustomer: () => Customer
|
|
||||||
CustomerCard: (props: CustomerCardProps) => ReactNode
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/types/order-deps.type.ts
|
|
||||||
export type OrderDeps = {
|
|
||||||
customer: Pick<CustomerApi, 'useCustomer'>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/types/order-factory.type.ts
|
|
||||||
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
|
||||||
```
|
|
||||||
|
|
||||||
### Фабрика без зависимостей
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/customer/customer.factory.ts
|
|
||||||
import type { CustomerFactory } from './types/customer-factory.type'
|
|
||||||
|
|
||||||
export const customerFactory: CustomerFactory = () => {
|
|
||||||
return {
|
|
||||||
useCustomer,
|
|
||||||
CustomerCard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Фабрика с зависимостями
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/order.factory.ts
|
|
||||||
import type { OrderFactory } from './types/order-factory.type'
|
|
||||||
|
|
||||||
export const orderFactory: OrderFactory = (deps) => {
|
|
||||||
return {
|
|
||||||
useOrder,
|
|
||||||
OrderCard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Композиция на уровне screen
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// screens/home/home.screen.tsx
|
|
||||||
import { customerFactory } from '@/business/customer'
|
|
||||||
import { orderFactory } from '@/business/order'
|
|
||||||
|
|
||||||
const customer = customerFactory()
|
|
||||||
const order = orderFactory({ customer })
|
|
||||||
|
|
||||||
const { useOrder, OrderCard } = order
|
|
||||||
|
|
||||||
export const HomeScreen = () => {
|
|
||||||
const currentOrder = useOrder()
|
|
||||||
|
|
||||||
return <OrderCard order={currentOrder} />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Жизненный цикл
|
## Жизненный цикл
|
||||||
|
|
||||||
|
|||||||
249
docs/examples/react/composition-provider.md
Normal file
249
docs/examples/react/composition-provider.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
title: Композиция через Provider
|
||||||
|
description: Пример композиции бизнес-фабрик screen-модуля через React Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
# Композиция через Provider
|
||||||
|
|
||||||
|
Раздел показывает, как screen-модуль может получить готовую композицию бизнес-доменов через React Context, не вызывая фабрики внутри себя.
|
||||||
|
|
||||||
|
## Идея
|
||||||
|
|
||||||
|
Screen получает готовый API бизнес-доменов через React Context. Граф фабрик собирается снаружи, например в роутере, а внутренние `parts/` достают нужные домены через хук без пропс-дриллинга.
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
1. **Принадлежность.** Provider, Context и хук принадлежат конкретному screen-модулю и лежат в его сегментах.
|
||||||
|
2. **Внутренний тип.** Тип композиции не экспортируется наружу — это закрывает доступ из бизнес-модулей.
|
||||||
|
3. **Внутренний хук.** Хук доступа не экспортируется — доступен только внутри screen и его `parts/`.
|
||||||
|
4. **Публичный Provider.** Только Provider экспортируется через `index.ts`, чтобы роутер мог обернуть screen.
|
||||||
|
5. **Сборка снаружи.** Граф фабрик собирается в роутере или другом композиторе, screen фабрики не вызывает.
|
||||||
|
6. **Запрет для бизнеса.** Бизнес-модули не используют провайдеры композиции. Cross-domain зависимости передаются только через аргументы фабрики.
|
||||||
|
|
||||||
|
## Структура модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/main/
|
||||||
|
├── main.screen.tsx
|
||||||
|
├── providers/
|
||||||
|
│ └── main-composition.provider.tsx
|
||||||
|
├── hooks/
|
||||||
|
│ └── use-main-composition.hook.ts
|
||||||
|
├── types/
|
||||||
|
│ └── main-composition.type.ts
|
||||||
|
├── parts/
|
||||||
|
│ └── featured-products/
|
||||||
|
│ ├── featured-products.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Сегмент `providers/` — расширение стандартного набора SLM. Спецификация это разрешает: команда сама определяет, какие сегменты используются.
|
||||||
|
|
||||||
|
## Распределение по сегментам
|
||||||
|
|
||||||
|
| Файл | Сегмент | Назначение |
|
||||||
|
|------|---------|------------|
|
||||||
|
| `main-composition.type.ts` | `types/` | TypeScript-тип композиции |
|
||||||
|
| `main-composition.provider.tsx` | `providers/` | Context и Provider-компонент |
|
||||||
|
| `use-main-composition.hook.ts` | `hooks/` | React-хук доступа |
|
||||||
|
| `main.screen.tsx` | корень | Корневой компонент screen-модуля |
|
||||||
|
| `featured-products/` | `parts/` | Вложенный модуль со своим публичным API |
|
||||||
|
|
||||||
|
## Тип композиции
|
||||||
|
|
||||||
|
Файл: `screens/main/types/main-composition.type.ts`.
|
||||||
|
|
||||||
|
Тип описывает, какие бизнес-домены доступны на этой странице. Он не экспортируется через `index.ts`, чтобы другие модули не зависели от внутренней формы композиции screen.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { CatalogApi } from '@/business/catalog'
|
||||||
|
import type { CartApi } from '@/business/cart'
|
||||||
|
|
||||||
|
export type MainComposition = {
|
||||||
|
catalog: CatalogApi
|
||||||
|
cart: CartApi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context и Provider
|
||||||
|
|
||||||
|
Файл: `screens/main/providers/main-composition.provider.tsx`.
|
||||||
|
|
||||||
|
Context — внутренняя деталь Provider, наружу он не экспортируется. Значение `null` по умолчанию нужно, чтобы хук мог проверить отсутствие Provider в дереве.
|
||||||
|
|
||||||
|
Provider-компонент экспортируется через `index.ts`. Роутер передаёт в `value` уже собранный граф фабрик со стабильной ссылкой.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createContext, type ReactNode } from 'react'
|
||||||
|
import type { MainComposition } from '../types/main-composition.type'
|
||||||
|
|
||||||
|
export const MainCompositionContext = createContext<MainComposition | null>(null)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: MainComposition
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MainCompositionProvider = ({ value, children }: Props) => (
|
||||||
|
<MainCompositionContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</MainCompositionContext.Provider>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Хук доступа
|
||||||
|
|
||||||
|
Файл: `screens/main/hooks/use-main-composition.hook.ts`.
|
||||||
|
|
||||||
|
Хук остаётся внутренним и не экспортируется через `index.ts` модуля. Он доступен только внутри screen и его `parts/`.
|
||||||
|
|
||||||
|
Если хук используется вне Provider, он бросает ошибку. Это даёт раннюю диагностику неправильной композиции дерева.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { MainCompositionContext } from '../providers/main-composition.provider'
|
||||||
|
|
||||||
|
export const useMainComposition = () => {
|
||||||
|
const ctx = useContext(MainCompositionContext)
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('useMainComposition must be used within MainCompositionProvider')
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка графа в роутере
|
||||||
|
|
||||||
|
Файл: `app/router.tsx`.
|
||||||
|
|
||||||
|
Роутер или другой композитор собирает граф фабрик в точке использования screen. Каждый домен получает свои зависимости через аргументы фабрики.
|
||||||
|
|
||||||
|
Фабрики вызываются вне React-компонента, если не зависят от runtime-параметров. Так API доменов не пересоздаётся на каждый рендер route-компонента.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { MainScreen, MainCompositionProvider } from '@/screens/main'
|
||||||
|
import { catalogFactory } from '@/business/catalog'
|
||||||
|
import { cartFactory } from '@/business/cart'
|
||||||
|
import { authFactory } from '@/business/auth'
|
||||||
|
|
||||||
|
const auth = authFactory()
|
||||||
|
const catalog = catalogFactory()
|
||||||
|
const cart = cartFactory({ auth })
|
||||||
|
|
||||||
|
const MainRoute = () => (
|
||||||
|
<MainCompositionProvider value={{ catalog, cart }}>
|
||||||
|
<MainScreen />
|
||||||
|
</MainCompositionProvider>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Корневой компонент screen
|
||||||
|
|
||||||
|
Файл: `screens/main/main.screen.tsx`.
|
||||||
|
|
||||||
|
Screen получает нужные домены из композиции и достаёт из API готовые хуки, компоненты или функции. В JSX используются уже локальные `useCategories` и `CategoryList`, а не обращение к фабричному API через точку.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useMainComposition } from './hooks/use-main-composition.hook'
|
||||||
|
import { FeaturedProducts } from './parts/featured-products'
|
||||||
|
|
||||||
|
export const MainScreen = () => {
|
||||||
|
const { catalog } = useMainComposition()
|
||||||
|
const { useCategories, CategoryList } = catalog
|
||||||
|
const categories = useCategories()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CategoryList categories={categories} />
|
||||||
|
<FeaturedProducts />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Вложенный part
|
||||||
|
|
||||||
|
Файл: `screens/main/parts/featured-products/featured-products.tsx`.
|
||||||
|
|
||||||
|
Вложенный модуль получает доступ к той же композиции родительского screen. Промежуточные компоненты не прокидывают домены через props.
|
||||||
|
|
||||||
|
Из API доменов достаются готовые сущности: `useFeatured`, `ProductCard` и `addItem`. Компонент работает с ними напрямую.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useMainComposition } from '../../hooks/use-main-composition.hook'
|
||||||
|
|
||||||
|
export const FeaturedProducts = () => {
|
||||||
|
const { catalog, cart } = useMainComposition()
|
||||||
|
const { useFeatured, ProductCard } = catalog
|
||||||
|
const { addItem } = cart
|
||||||
|
const products = useFeatured()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{products.map((product) => (
|
||||||
|
<ProductCard
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
onAdd={() => addItem(product.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Файл: `screens/main/parts/featured-products/index.ts`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { FeaturedProducts } from './featured-products'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Публичный API screen-модуля
|
||||||
|
|
||||||
|
Файл: `screens/main/index.ts`.
|
||||||
|
|
||||||
|
Наружу экспортируются только screen и его Provider. `MainComposition`, `MainCompositionContext` и `useMainComposition` остаются деталями реализации.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { MainScreen } from './main.screen'
|
||||||
|
export { MainCompositionProvider } from './providers/main-composition.provider'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему тип композиции не экспортируется
|
||||||
|
|
||||||
|
Внутренний тип закрывает доступ к форме композиции из внешних модулей. Бизнес-модуль не должен знать, какие домены собраны для конкретного screen.
|
||||||
|
|
||||||
|
Такой импорт из бизнес-модуля не должен быть возможен через публичный API screen.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { MainComposition } from '@/screens/main'
|
||||||
|
```
|
||||||
|
|
||||||
|
Когда тип остаётся внутренним, такая связь невозможна через публичный API screen-модуля.
|
||||||
|
|
||||||
|
## Почему хук не экспортируется
|
||||||
|
|
||||||
|
Если хук доступа сделать публичным, любой модуль сможет вызвать его напрямую. Внутренний хук доступен только через относительные импорты внутри screen-модуля и его `parts/`.
|
||||||
|
|
||||||
|
## Почему Provider экспортируется
|
||||||
|
|
||||||
|
Provider безопасно экспортировать: сам по себе он не даёт доступ к данным, а только принимает готовую композицию и передаёт её детям внутри React-дерева.
|
||||||
|
|
||||||
|
## Стабильность value
|
||||||
|
|
||||||
|
Фабрики создаются на уровне модуля, поэтому `catalog` и `cart` сохраняют ссылки между рендерами `MainRoute`.
|
||||||
|
|
||||||
|
Если домены зависят от runtime-параметров, граф нужно собирать в отдельном композиторе для этих параметров и передавать в Provider уже готовую композицию.
|
||||||
|
|
||||||
|
## Расширение на другие screen-модули
|
||||||
|
|
||||||
|
Паттерн повторяется для каждого screen, которому нужна композиция бизнес-доменов.
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/checkout/providers/checkout-composition.provider.tsx
|
||||||
|
screens/checkout/hooks/use-checkout-composition.hook.ts
|
||||||
|
screens/checkout/types/checkout-composition.type.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Имена включают имя screen-модуля. Не используйте универсальные названия вроде `useComposition` или `useScope`: по имени файла должно быть понятно, к какой странице привязан Context.
|
||||||
52
docs/examples/react/factory-composition.md
Normal file
52
docs/examples/react/factory-composition.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: Композиция фабрик
|
||||||
|
description: Пример композиции business-фабрик на уровне screen-модуля в React-проекте
|
||||||
|
---
|
||||||
|
|
||||||
|
# Композиция фабрик
|
||||||
|
|
||||||
|
Раздел показывает, как собрать API нескольких business-модулей в React screen-модуле. Пример подходит для простой композиции, когда screen сам является точкой использования доменов.
|
||||||
|
|
||||||
|
## Идея
|
||||||
|
|
||||||
|
Композиция фабрик выполняется в модуле-потребителе: screen, layout или другом модуле группы «Композиция». Business-модули не импортируют runtime-код друг друга напрямую, а cross-domain зависимости получают только через аргументы фабрик.
|
||||||
|
|
||||||
|
## Структура screen-модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/home/
|
||||||
|
├── home.screen.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка фабрик
|
||||||
|
|
||||||
|
Файл: `screens/home/home.screen.tsx`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { customerFactory } from '@/business/customer'
|
||||||
|
import { orderFactory } from '@/business/order'
|
||||||
|
|
||||||
|
const customer = customerFactory()
|
||||||
|
const order = orderFactory({ customer })
|
||||||
|
|
||||||
|
const { useOrder, OrderCard } = order
|
||||||
|
|
||||||
|
export const HomeScreen = () => {
|
||||||
|
const currentOrder = useOrder()
|
||||||
|
|
||||||
|
return <OrderCard order={currentOrder} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`customerFactory` создаётся первой, потому что `orderFactory` зависит от части API домена `customer`. Модуль `order` не импортирует `customer` в runtime — зависимость передаётся снаружи.
|
||||||
|
|
||||||
|
## Публичный API screen-модуля
|
||||||
|
|
||||||
|
Файл: `screens/home/index.ts`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { HomeScreen } from './home.screen'
|
||||||
|
```
|
||||||
|
|
||||||
|
Screen экспортирует только собственный публичный API. Собранные экземпляры business API остаются деталями реализации screen-модуля.
|
||||||
114
docs/examples/react/factory.md
Normal file
114
docs/examples/react/factory.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
title: Создание фабрики
|
||||||
|
description: Пример создания фабрики business-модуля в React-проекте
|
||||||
|
---
|
||||||
|
|
||||||
|
# Создание фабрики
|
||||||
|
|
||||||
|
Раздел показывает, как оформить фабрику business-модуля в React-проекте: описать публичный API, зависимости и функцию, возвращающую runtime API.
|
||||||
|
|
||||||
|
## Структура business-модуля
|
||||||
|
|
||||||
|
Фабрика лежит в корне business-модуля. Типы публичного API и зависимостей размещаются в `types/`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
business/customer/
|
||||||
|
├── customer.factory.ts
|
||||||
|
├── hooks/
|
||||||
|
├── types/
|
||||||
|
│ ├── customer.type.ts
|
||||||
|
│ ├── customer-api.type.ts
|
||||||
|
│ └── customer-factory.type.ts
|
||||||
|
├── ui/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Тип публичного API
|
||||||
|
|
||||||
|
Публичный API описывает runtime-возможности, которые модуль отдаёт потребителям: хуки, компоненты и сценарные методы.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/types/customer-api.type.ts
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import type { Customer } from './customer.type'
|
||||||
|
|
||||||
|
export type CustomerCardProps = {
|
||||||
|
customer: Customer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomerApi = {
|
||||||
|
useCustomer: () => Customer | null
|
||||||
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/types/customer-factory.type.ts
|
||||||
|
import type { CustomerApi } from './customer-api.type'
|
||||||
|
|
||||||
|
export type CustomerFactory = () => CustomerApi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Фабрика без зависимостей
|
||||||
|
|
||||||
|
Если модулю не нужны другие домены в runtime, фабрика создаётся без аргументов.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/customer.factory.ts
|
||||||
|
import { useCustomer } from './hooks/use-customer.hook'
|
||||||
|
import { CustomerCard } from './ui/customer-card'
|
||||||
|
import type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
|
||||||
|
export const customerFactory: CustomerFactory = () => {
|
||||||
|
return {
|
||||||
|
useCustomer,
|
||||||
|
CustomerCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/index.ts
|
||||||
|
export { customerFactory } from './customer.factory'
|
||||||
|
|
||||||
|
export type { Customer } from './types/customer.type'
|
||||||
|
export type { CustomerApi } from './types/customer-api.type'
|
||||||
|
export type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Фабрика с зависимостями
|
||||||
|
|
||||||
|
Если модулю нужен другой домен в runtime, зависимость передаётся аргументом фабрики. Тип зависимости описывает только нужную часть API.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-deps.type.ts
|
||||||
|
import type { CustomerApi } from '@/business/customer'
|
||||||
|
|
||||||
|
export type OrderDeps = {
|
||||||
|
customer: Pick<CustomerApi, 'useCustomer'>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-factory.type.ts
|
||||||
|
import type { OrderApi } from './order-api.type'
|
||||||
|
import type { OrderDeps } from './order-deps.type'
|
||||||
|
|
||||||
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/order.factory.ts
|
||||||
|
import { createUseOrder } from './hooks/use-order.hook'
|
||||||
|
import { OrderCard } from './ui/order-card'
|
||||||
|
import type { OrderFactory } from './types/order-factory.type'
|
||||||
|
|
||||||
|
export const orderFactory: OrderFactory = (deps) => {
|
||||||
|
const useOrder = createUseOrder(deps)
|
||||||
|
|
||||||
|
return {
|
||||||
|
useOrder,
|
||||||
|
OrderCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -556,87 +556,11 @@ Business-модуль всегда экспортирует фабрику. Фа
|
|||||||
|
|
||||||
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
||||||
|
|
||||||
#### Структура business-модуля
|
#### Примеры
|
||||||
|
|
||||||
```text
|
Пример реализации фабрики в React см. в [Создание фабрики](/examples/react/factory).
|
||||||
business/customer/
|
|
||||||
├── customer.factory.ts
|
|
||||||
├── index.ts
|
|
||||||
└── types/
|
|
||||||
├── customer.type.ts
|
|
||||||
├── customer-api.type.ts
|
|
||||||
├── customer-deps.type.ts
|
|
||||||
└── customer-factory.type.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типы
|
Пример композиции фабрик в React screen-модуле см. в [Композиция фабрик](/examples/react/factory-composition).
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/customer/types/customer-api.type.ts
|
|
||||||
export type CustomerApi = {
|
|
||||||
useCustomer: () => Customer
|
|
||||||
CustomerCard: (props: CustomerCardProps) => ReactNode
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/types/order-deps.type.ts
|
|
||||||
export type OrderDeps = {
|
|
||||||
customer: Pick<CustomerApi, 'useCustomer'>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/types/order-factory.type.ts
|
|
||||||
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Фабрика без зависимостей
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/customer/customer.factory.ts
|
|
||||||
import type { CustomerFactory } from './types/customer-factory.type'
|
|
||||||
|
|
||||||
export const customerFactory: CustomerFactory = () => {
|
|
||||||
return {
|
|
||||||
useCustomer,
|
|
||||||
CustomerCard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Фабрика с зависимостями
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/order/order.factory.ts
|
|
||||||
import type { OrderFactory } from './types/order-factory.type'
|
|
||||||
|
|
||||||
export const orderFactory: OrderFactory = (deps) => {
|
|
||||||
return {
|
|
||||||
useOrder,
|
|
||||||
OrderCard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Композиция на уровне screen
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// screens/home/home.screen.tsx
|
|
||||||
import { customerFactory } from '@/business/customer'
|
|
||||||
import { orderFactory } from '@/business/order'
|
|
||||||
|
|
||||||
const customer = customerFactory()
|
|
||||||
const order = orderFactory({ customer })
|
|
||||||
|
|
||||||
const { useOrder, OrderCard } = order
|
|
||||||
|
|
||||||
export const HomeScreen = () => {
|
|
||||||
const currentOrder = useOrder()
|
|
||||||
|
|
||||||
return <OrderCard order={currentOrder} />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Жизненный цикл
|
### Жизненный цикл
|
||||||
|
|
||||||
@@ -1058,4 +982,410 @@ packages/ui/promo-section/
|
|||||||
- Пакеты не импортируют приложения.
|
- Пакеты не импортируют приложения.
|
||||||
- Межпакетные импорты идут только через публичный API.
|
- Межпакетные импорты идут только через публичный API.
|
||||||
- Deep imports внутрь пакетов запрещены.
|
- Deep imports внутрь пакетов запрещены.
|
||||||
- Локальная колокация важнее преждевременного выноса в `packages/*`.
|
- Локальная колокация важнее преждевременного выноса в `packages/*`.
|
||||||
|
|
||||||
|
<!-- /docs/examples/react/factory -->
|
||||||
|
## Создание фабрики
|
||||||
|
|
||||||
|
Раздел показывает, как оформить фабрику business-модуля в React-проекте: описать публичный API, зависимости и функцию, возвращающую runtime API.
|
||||||
|
|
||||||
|
### Структура business-модуля
|
||||||
|
|
||||||
|
Фабрика лежит в корне business-модуля. Типы публичного API и зависимостей размещаются в `types/`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
business/customer/
|
||||||
|
├── customer.factory.ts
|
||||||
|
├── hooks/
|
||||||
|
├── types/
|
||||||
|
│ ├── customer.type.ts
|
||||||
|
│ ├── customer-api.type.ts
|
||||||
|
│ └── customer-factory.type.ts
|
||||||
|
├── ui/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тип публичного API
|
||||||
|
|
||||||
|
Публичный API описывает runtime-возможности, которые модуль отдаёт потребителям: хуки, компоненты и сценарные методы.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/types/customer-api.type.ts
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import type { Customer } from './customer.type'
|
||||||
|
|
||||||
|
export type CustomerCardProps = {
|
||||||
|
customer: Customer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomerApi = {
|
||||||
|
useCustomer: () => Customer | null
|
||||||
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/types/customer-factory.type.ts
|
||||||
|
import type { CustomerApi } from './customer-api.type'
|
||||||
|
|
||||||
|
export type CustomerFactory = () => CustomerApi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика без зависимостей
|
||||||
|
|
||||||
|
Если модулю не нужны другие домены в runtime, фабрика создаётся без аргументов.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/customer.factory.ts
|
||||||
|
import { useCustomer } from './hooks/use-customer.hook'
|
||||||
|
import { CustomerCard } from './ui/customer-card'
|
||||||
|
import type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
|
||||||
|
export const customerFactory: CustomerFactory = () => {
|
||||||
|
return {
|
||||||
|
useCustomer,
|
||||||
|
CustomerCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/index.ts
|
||||||
|
export { customerFactory } from './customer.factory'
|
||||||
|
|
||||||
|
export type { Customer } from './types/customer.type'
|
||||||
|
export type { CustomerApi } from './types/customer-api.type'
|
||||||
|
export type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика с зависимостями
|
||||||
|
|
||||||
|
Если модулю нужен другой домен в runtime, зависимость передаётся аргументом фабрики. Тип зависимости описывает только нужную часть API.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-deps.type.ts
|
||||||
|
import type { CustomerApi } from '@/business/customer'
|
||||||
|
|
||||||
|
export type OrderDeps = {
|
||||||
|
customer: Pick<CustomerApi, 'useCustomer'>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-factory.type.ts
|
||||||
|
import type { OrderApi } from './order-api.type'
|
||||||
|
import type { OrderDeps } from './order-deps.type'
|
||||||
|
|
||||||
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/order.factory.ts
|
||||||
|
import { createUseOrder } from './hooks/use-order.hook'
|
||||||
|
import { OrderCard } from './ui/order-card'
|
||||||
|
import type { OrderFactory } from './types/order-factory.type'
|
||||||
|
|
||||||
|
export const orderFactory: OrderFactory = (deps) => {
|
||||||
|
const useOrder = createUseOrder(deps)
|
||||||
|
|
||||||
|
return {
|
||||||
|
useOrder,
|
||||||
|
OrderCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- /docs/examples/react/factory-composition -->
|
||||||
|
## Композиция фабрик
|
||||||
|
|
||||||
|
Раздел показывает, как собрать API нескольких business-модулей в React screen-модуле. Пример подходит для простой композиции, когда screen сам является точкой использования доменов.
|
||||||
|
|
||||||
|
### Идея
|
||||||
|
|
||||||
|
Композиция фабрик выполняется в модуле-потребителе: screen, layout или другом модуле группы «Композиция». Business-модули не импортируют runtime-код друг друга напрямую, а cross-domain зависимости получают только через аргументы фабрик.
|
||||||
|
|
||||||
|
### Структура screen-модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/home/
|
||||||
|
├── home.screen.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сборка фабрик
|
||||||
|
|
||||||
|
Файл: `screens/home/home.screen.tsx`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { customerFactory } from '@/business/customer'
|
||||||
|
import { orderFactory } from '@/business/order'
|
||||||
|
|
||||||
|
const customer = customerFactory()
|
||||||
|
const order = orderFactory({ customer })
|
||||||
|
|
||||||
|
const { useOrder, OrderCard } = order
|
||||||
|
|
||||||
|
export const HomeScreen = () => {
|
||||||
|
const currentOrder = useOrder()
|
||||||
|
|
||||||
|
return <OrderCard order={currentOrder} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`customerFactory` создаётся первой, потому что `orderFactory` зависит от части API домена `customer`. Модуль `order` не импортирует `customer` в runtime — зависимость передаётся снаружи.
|
||||||
|
|
||||||
|
### Публичный API screen-модуля
|
||||||
|
|
||||||
|
Файл: `screens/home/index.ts`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { HomeScreen } from './home.screen'
|
||||||
|
```
|
||||||
|
|
||||||
|
Screen экспортирует только собственный публичный API. Собранные экземпляры business API остаются деталями реализации screen-модуля.
|
||||||
|
|
||||||
|
<!-- /docs/examples/react/composition-provider -->
|
||||||
|
## Композиция через Provider
|
||||||
|
|
||||||
|
Раздел показывает, как screen-модуль может получить готовую композицию бизнес-доменов через React Context, не вызывая фабрики внутри себя.
|
||||||
|
|
||||||
|
### Идея
|
||||||
|
|
||||||
|
Screen получает готовый API бизнес-доменов через React Context. Граф фабрик собирается снаружи, например в роутере, а внутренние `parts/` достают нужные домены через хук без пропс-дриллинга.
|
||||||
|
|
||||||
|
### Принципы
|
||||||
|
|
||||||
|
1. **Принадлежность.** Provider, Context и хук принадлежат конкретному screen-модулю и лежат в его сегментах.
|
||||||
|
2. **Внутренний тип.** Тип композиции не экспортируется наружу — это закрывает доступ из бизнес-модулей.
|
||||||
|
3. **Внутренний хук.** Хук доступа не экспортируется — доступен только внутри screen и его `parts/`.
|
||||||
|
4. **Публичный Provider.** Только Provider экспортируется через `index.ts`, чтобы роутер мог обернуть screen.
|
||||||
|
5. **Сборка снаружи.** Граф фабрик собирается в роутере или другом композиторе, screen фабрики не вызывает.
|
||||||
|
6. **Запрет для бизнеса.** Бизнес-модули не используют провайдеры композиции. Cross-domain зависимости передаются только через аргументы фабрики.
|
||||||
|
|
||||||
|
### Структура модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/main/
|
||||||
|
├── main.screen.tsx
|
||||||
|
├── providers/
|
||||||
|
│ └── main-composition.provider.tsx
|
||||||
|
├── hooks/
|
||||||
|
│ └── use-main-composition.hook.ts
|
||||||
|
├── types/
|
||||||
|
│ └── main-composition.type.ts
|
||||||
|
├── parts/
|
||||||
|
│ └── featured-products/
|
||||||
|
│ ├── featured-products.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Сегмент `providers/` — расширение стандартного набора SLM. Спецификация это разрешает: команда сама определяет, какие сегменты используются.
|
||||||
|
|
||||||
|
### Распределение по сегментам
|
||||||
|
|
||||||
|
| Файл | Сегмент | Назначение |
|
||||||
|
|------|---------|------------|
|
||||||
|
| `main-composition.type.ts` | `types/` | TypeScript-тип композиции |
|
||||||
|
| `main-composition.provider.tsx` | `providers/` | Context и Provider-компонент |
|
||||||
|
| `use-main-composition.hook.ts` | `hooks/` | React-хук доступа |
|
||||||
|
| `main.screen.tsx` | корень | Корневой компонент screen-модуля |
|
||||||
|
| `featured-products/` | `parts/` | Вложенный модуль со своим публичным API |
|
||||||
|
|
||||||
|
### Тип композиции
|
||||||
|
|
||||||
|
Файл: `screens/main/types/main-composition.type.ts`.
|
||||||
|
|
||||||
|
Тип описывает, какие бизнес-домены доступны на этой странице. Он не экспортируется через `index.ts`, чтобы другие модули не зависели от внутренней формы композиции screen.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { CatalogApi } from '@/business/catalog'
|
||||||
|
import type { CartApi } from '@/business/cart'
|
||||||
|
|
||||||
|
export type MainComposition = {
|
||||||
|
catalog: CatalogApi
|
||||||
|
cart: CartApi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context и Provider
|
||||||
|
|
||||||
|
Файл: `screens/main/providers/main-composition.provider.tsx`.
|
||||||
|
|
||||||
|
Context — внутренняя деталь Provider, наружу он не экспортируется. Значение `null` по умолчанию нужно, чтобы хук мог проверить отсутствие Provider в дереве.
|
||||||
|
|
||||||
|
Provider-компонент экспортируется через `index.ts`. Роутер передаёт в `value` уже собранный граф фабрик со стабильной ссылкой.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createContext, type ReactNode } from 'react'
|
||||||
|
import type { MainComposition } from '../types/main-composition.type'
|
||||||
|
|
||||||
|
export const MainCompositionContext = createContext<MainComposition | null>(null)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: MainComposition
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MainCompositionProvider = ({ value, children }: Props) => (
|
||||||
|
<MainCompositionContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</MainCompositionContext.Provider>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Хук доступа
|
||||||
|
|
||||||
|
Файл: `screens/main/hooks/use-main-composition.hook.ts`.
|
||||||
|
|
||||||
|
Хук остаётся внутренним и не экспортируется через `index.ts` модуля. Он доступен только внутри screen и его `parts/`.
|
||||||
|
|
||||||
|
Если хук используется вне Provider, он бросает ошибку. Это даёт раннюю диагностику неправильной композиции дерева.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { MainCompositionContext } from '../providers/main-composition.provider'
|
||||||
|
|
||||||
|
export const useMainComposition = () => {
|
||||||
|
const ctx = useContext(MainCompositionContext)
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('useMainComposition must be used within MainCompositionProvider')
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сборка графа в роутере
|
||||||
|
|
||||||
|
Файл: `app/router.tsx`.
|
||||||
|
|
||||||
|
Роутер или другой композитор собирает граф фабрик в точке использования screen. Каждый домен получает свои зависимости через аргументы фабрики.
|
||||||
|
|
||||||
|
Фабрики вызываются вне React-компонента, если не зависят от runtime-параметров. Так API доменов не пересоздаётся на каждый рендер route-компонента.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { MainScreen, MainCompositionProvider } from '@/screens/main'
|
||||||
|
import { catalogFactory } from '@/business/catalog'
|
||||||
|
import { cartFactory } from '@/business/cart'
|
||||||
|
import { authFactory } from '@/business/auth'
|
||||||
|
|
||||||
|
const auth = authFactory()
|
||||||
|
const catalog = catalogFactory()
|
||||||
|
const cart = cartFactory({ auth })
|
||||||
|
|
||||||
|
const MainRoute = () => (
|
||||||
|
<MainCompositionProvider value={{ catalog, cart }}>
|
||||||
|
<MainScreen />
|
||||||
|
</MainCompositionProvider>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Корневой компонент screen
|
||||||
|
|
||||||
|
Файл: `screens/main/main.screen.tsx`.
|
||||||
|
|
||||||
|
Screen получает нужные домены из композиции и достаёт из API готовые хуки, компоненты или функции. В JSX используются уже локальные `useCategories` и `CategoryList`, а не обращение к фабричному API через точку.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useMainComposition } from './hooks/use-main-composition.hook'
|
||||||
|
import { FeaturedProducts } from './parts/featured-products'
|
||||||
|
|
||||||
|
export const MainScreen = () => {
|
||||||
|
const { catalog } = useMainComposition()
|
||||||
|
const { useCategories, CategoryList } = catalog
|
||||||
|
const categories = useCategories()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CategoryList categories={categories} />
|
||||||
|
<FeaturedProducts />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вложенный part
|
||||||
|
|
||||||
|
Файл: `screens/main/parts/featured-products/featured-products.tsx`.
|
||||||
|
|
||||||
|
Вложенный модуль получает доступ к той же композиции родительского screen. Промежуточные компоненты не прокидывают домены через props.
|
||||||
|
|
||||||
|
Из API доменов достаются готовые сущности: `useFeatured`, `ProductCard` и `addItem`. Компонент работает с ними напрямую.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useMainComposition } from '../../hooks/use-main-composition.hook'
|
||||||
|
|
||||||
|
export const FeaturedProducts = () => {
|
||||||
|
const { catalog, cart } = useMainComposition()
|
||||||
|
const { useFeatured, ProductCard } = catalog
|
||||||
|
const { addItem } = cart
|
||||||
|
const products = useFeatured()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{products.map((product) => (
|
||||||
|
<ProductCard
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
onAdd={() => addItem(product.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Файл: `screens/main/parts/featured-products/index.ts`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { FeaturedProducts } from './featured-products'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Публичный API screen-модуля
|
||||||
|
|
||||||
|
Файл: `screens/main/index.ts`.
|
||||||
|
|
||||||
|
Наружу экспортируются только screen и его Provider. `MainComposition`, `MainCompositionContext` и `useMainComposition` остаются деталями реализации.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { MainScreen } from './main.screen'
|
||||||
|
export { MainCompositionProvider } from './providers/main-composition.provider'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Почему тип композиции не экспортируется
|
||||||
|
|
||||||
|
Внутренний тип закрывает доступ к форме композиции из внешних модулей. Бизнес-модуль не должен знать, какие домены собраны для конкретного screen.
|
||||||
|
|
||||||
|
Такой импорт из бизнес-модуля не должен быть возможен через публичный API screen.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { MainComposition } from '@/screens/main'
|
||||||
|
```
|
||||||
|
|
||||||
|
Когда тип остаётся внутренним, такая связь невозможна через публичный API screen-модуля.
|
||||||
|
|
||||||
|
### Почему хук не экспортируется
|
||||||
|
|
||||||
|
Если хук доступа сделать публичным, любой модуль сможет вызвать его напрямую. Внутренний хук доступен только через относительные импорты внутри screen-модуля и его `parts/`.
|
||||||
|
|
||||||
|
### Почему Provider экспортируется
|
||||||
|
|
||||||
|
Provider безопасно экспортировать: сам по себе он не даёт доступ к данным, а только принимает готовую композицию и передаёт её детям внутри React-дерева.
|
||||||
|
|
||||||
|
### Стабильность value
|
||||||
|
|
||||||
|
Фабрики создаются на уровне модуля, поэтому `catalog` и `cart` сохраняют ссылки между рендерами `MainRoute`.
|
||||||
|
|
||||||
|
Если домены зависят от runtime-параметров, граф нужно собирать в отдельном композиторе для этих параметров и передавать в Provider уже готовую композицию.
|
||||||
|
|
||||||
|
### Расширение на другие screen-модули
|
||||||
|
|
||||||
|
Паттерн повторяется для каждого screen, которому нужна композиция бизнес-доменов.
|
||||||
|
|
||||||
|
```text
|
||||||
|
screens/checkout/providers/checkout-composition.provider.tsx
|
||||||
|
screens/checkout/hooks/use-checkout-composition.hook.ts
|
||||||
|
screens/checkout/types/checkout-composition.type.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Имена включают имя screen-модуля. Не используйте универсальные названия вроде `useComposition` или `useScope`: по имени файла должно быть понятно, к какой странице привязан Context.
|
||||||
Reference in New Issue
Block a user