141 lines
3.7 KiB
Markdown
141 lines
3.7 KiB
Markdown
# Project Structure
|
|
|
|
This document outlines the structure of our application, which follows a domain-driven, Flux-style architecture using Effector for state management.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
src/
|
|
app/ # Application entry point and configuration
|
|
root.tsx # Root component
|
|
routes.tsx # Route definitions
|
|
domains/ # Feature domains
|
|
<feature>/ # e.g., products, auth, orders
|
|
model.ts # Effector events, stores, effects
|
|
view-model.ts # React hooks for connecting to the model
|
|
ui/ # React components
|
|
index.ts # Exports all components
|
|
Component.tsx # Individual components
|
|
shared/ # Shared code
|
|
lib/ # Utilities, helpers, adapters
|
|
ui/ # Reusable UI components
|
|
tests/ # Test files
|
|
```
|
|
|
|
## Domain Structure
|
|
|
|
Each domain follows a consistent structure:
|
|
|
|
### model.ts
|
|
|
|
Contains all Effector-related code:
|
|
- Events (actions)
|
|
- Stores (state)
|
|
- Effects (async operations)
|
|
|
|
Example:
|
|
```typescript
|
|
// domains/products/model.ts
|
|
import { createStore, createEvent, createEffect } from 'effector';
|
|
import { ProductsViewsService } from '@lib/api/merchant/services/ProductsViewsService';
|
|
import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto';
|
|
|
|
// Events
|
|
export const setProductsFilter = createEvent<Partial<productQueryRequestDto>>();
|
|
|
|
// Effects
|
|
export const fetchProductsFx = createEffect(async (filter: productQueryRequestDto) => {
|
|
return await ProductsViewsService.postApiProductCompositeQueryByCanonical({
|
|
requestBody: filter
|
|
});
|
|
});
|
|
|
|
// Stores
|
|
export const $products = createStore<productCompositeDto[]>([])
|
|
.on(fetchProductsFx.doneData, (_, payload) => Array.isArray(payload) ? payload : [payload]);
|
|
```
|
|
|
|
### view-model.ts
|
|
|
|
Contains React hooks that connect to the Effector model:
|
|
|
|
```typescript
|
|
// domains/products/view-model.ts
|
|
import { useStore } from 'effector-react';
|
|
import { useCallback } from 'react';
|
|
import {
|
|
$products,
|
|
$productsLoading,
|
|
setProductsFilter,
|
|
fetchProductsFx
|
|
} from './model';
|
|
|
|
export const useProducts = () => {
|
|
const products = useStore($products);
|
|
const isLoading = useStore($productsLoading);
|
|
|
|
const updateFilter = useCallback((newFilter) => {
|
|
setProductsFilter(newFilter);
|
|
}, []);
|
|
|
|
return {
|
|
products,
|
|
isLoading,
|
|
updateFilter
|
|
};
|
|
};
|
|
```
|
|
|
|
### ui/
|
|
|
|
Contains React components that use the view-model:
|
|
|
|
```typescript
|
|
// domains/products/ui/ProductsList.tsx
|
|
import React from 'react';
|
|
import { useProducts } from '../view-model';
|
|
import { ProductCard } from './ProductCard';
|
|
|
|
export const ProductsList: React.FC = () => {
|
|
const { products, isLoading } = useProducts();
|
|
|
|
if (isLoading) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{products.map(product => (
|
|
<ProductCard key={product.id} product={product} />
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
## Shared Code
|
|
|
|
### shared/lib/
|
|
|
|
Contains utilities, helpers, and adapters that are used across multiple domains.
|
|
|
|
### shared/ui/
|
|
|
|
Contains reusable UI components that are not tied to any specific domain.
|
|
|
|
## Tests
|
|
|
|
Tests are organized alongside the code they test, following the same structure.
|
|
|
|
## Best Practices
|
|
|
|
1. **Keep domains isolated**: Each domain should be self-contained and not depend on other domains.
|
|
|
|
2. **Use named exports**: Avoid default exports to make refactoring easier.
|
|
|
|
3. **Keep files small**: Each file should have a single responsibility.
|
|
|
|
4. **Use TypeScript**: All files should be written in TypeScript for type safety.
|
|
|
|
5. **Follow Flux pattern**: Maintain unidirectional data flow from events to stores to views.
|