134 lines
4.0 KiB
Markdown
134 lines
4.0 KiB
Markdown
# Architecture: Flux-style with Effector
|
|
|
|
## 1. Terminology & Core Concepts
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| SPA (Single-Page Application) | Entire UI is boot-strapped once in the browser; further navigation is handled client-side. |
|
|
| Client-Side Rendering (CSR) | The browser runs JavaScript (React) that turns JSON/state into DOM; no HTML is streamed from the server after first load. |
|
|
| Effector | A small TypeScript-first library that implements Flux-style one-way data-flow with four primitives (event, store, effect, domain). |
|
|
| Flux-style architecture | Events mutate stores ➜ stores notify subscribed views; data always moves in one direction. |
|
|
| Effector-based Flux architecture | Using Effector as the single source of truth for state inside a React SPA. |
|
|
|
|
## 2. Effector Primitives → Flux Mapping
|
|
|
|
```
|
|
createEvent() ≈ Action
|
|
(create)Store() ≈ Store
|
|
createEffect() ≈ Async side-effect handler
|
|
Domains ≈ Namespace / module boundary
|
|
View ≈ React component using `useStore` / `useUnit`
|
|
```
|
|
|
|
Flow diagram:
|
|
|
|
```
|
|
event ─┐
|
|
│ triggers
|
|
store ──┤ update → React re-render
|
|
│
|
|
effect ←┘ (async; dispatches success/fail events)
|
|
```
|
|
|
|
## 3. Project Layout
|
|
|
|
```
|
|
src/
|
|
app/
|
|
root.tsx # <App/>, routing
|
|
routes.tsx # Route definitions
|
|
domains/
|
|
<feature>/
|
|
model.ts # Effector events, stores, effects (pure, testable)
|
|
view-model.ts # Thin hooks that adapt model to React
|
|
ui/ # Dumb presentational components
|
|
shared/
|
|
ui/ # Design-system atoms
|
|
lib/ # Generic helpers
|
|
tests/ # Test files
|
|
```
|
|
|
|
## 4. Coding Guidelines
|
|
|
|
| Guideline | Why it helps |
|
|
|-----------|--------------|
|
|
| TypeScript strict mode | Types give static guarantees for safe edits. |
|
|
| Pure functions in model.ts | Deterministic behavior is easier to reason about. |
|
|
| Named exports only | Eliminates default-export rename hazards. |
|
|
| Small files, single responsibility | Enables chunked, parallel code transforms. |
|
|
| Unit tests co-located with domain logic | Provide regression oracles for automated changes. |
|
|
| Runtime schema checks (e.g., Zod) | Fail fast on unexpected data. |
|
|
|
|
## 5. Implementation Example
|
|
|
|
### model.ts
|
|
```typescript
|
|
import { createStore, createEvent, createEffect } from 'effector';
|
|
import { ProductsViewsService } from '@lib/api/merchant/services/ProductsViewsService';
|
|
import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto';
|
|
|
|
// Define events
|
|
export const setProductsFilter = createEvent<Partial<productQueryRequestDto>>();
|
|
|
|
// Define effects
|
|
export const fetchProductsFx = createEffect(async (filter: productQueryRequestDto) => {
|
|
return await ProductsViewsService.postApiProductCompositeQueryByCanonical({
|
|
requestBody: filter
|
|
});
|
|
});
|
|
|
|
// Create stores
|
|
export const $products = createStore<productCompositeDto[]>([])
|
|
.on(fetchProductsFx.doneData, (_, payload) => Array.isArray(payload) ? payload : [payload]);
|
|
```
|
|
|
|
### view-model.ts
|
|
```typescript
|
|
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 Component
|
|
```typescript
|
|
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>
|
|
);
|
|
};
|
|
```
|