react-spa-template/docs/effector-guide.md
2025-04-19 19:24:27 +02:00

213 lines
4.8 KiB
Markdown

# Effector Guide
This guide provides an overview of how to use Effector in our application, following our Flux-style architecture.
## Core Concepts
Effector is a state management library that implements the Flux pattern with three main primitives:
1. **Events**: Trigger state changes
2. **Stores**: Hold application state
3. **Effects**: Handle side effects (async operations)
## Basic Usage
### Creating Events
Events are functions that can be called to trigger state changes:
```typescript
import { createEvent } from 'effector';
// Create an event
export const increment = createEvent();
export const addTodo = createEvent<string>();
export const updateUser = createEvent<{ name: string, email: string }>();
// Call the event
increment();
addTodo('Buy milk');
updateUser({ name: 'John', email: 'john@example.com' });
```
### Creating Stores
Stores hold the application state and update in response to events:
```typescript
import { createStore } from 'effector';
import { increment, addTodo } from './events';
// Create a store with initial state
export const $counter = createStore(0)
.on(increment, state => state + 1);
export const $todos = createStore<string[]>([])
.on(addTodo, (state, todo) => [...state, todo]);
```
### Creating Effects
Effects handle asynchronous operations:
```typescript
import { createEffect } from 'effector';
import { api } from '@shared/lib/api';
// Create an effect
export const fetchUserFx = createEffect(async (userId: string) => {
const response = await api.getUser(userId);
return response.data;
});
// Call the effect
fetchUserFx('123');
```
### Handling Effect States
Effects have three states: pending, done, and fail:
```typescript
import { createStore } from 'effector';
import { fetchUserFx } from './effects';
// Create stores for different effect states
export const $isLoading = createStore(false)
.on(fetchUserFx.pending, (_, isPending) => isPending);
export const $user = createStore(null)
.on(fetchUserFx.doneData, (_, user) => user);
export const $error = createStore(null)
.on(fetchUserFx.failData, (_, error) => error)
.on(fetchUserFx, () => null); // Reset error when effect is called
```
## Using with React
### useStore Hook
The `useStore` hook connects Effector stores to React components:
```typescript
import { useStore } from 'effector-react';
import { $counter, increment } from './model';
const Counter = () => {
const count = useStore($counter);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => increment()}>Increment</button>
</div>
);
};
```
### useEvent Hook
The `useEvent` hook creates a stable callback for events:
```typescript
import { useStore, useEvent } from 'effector-react';
import { $todos, addTodo } from './model';
const TodoList = () => {
const todos = useStore($todos);
const handleAddTodo = useEvent(addTodo);
return (
<div>
<button onClick={() => handleAddTodo('New todo')}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
};
```
## Advanced Patterns
### Derived Stores (Computed Values)
Create derived stores using the `.map` method:
```typescript
import { createStore } from 'effector';
export const $todos = createStore([
{ id: 1, text: 'Buy milk', completed: false },
{ id: 2, text: 'Clean house', completed: true }
]);
// Derived store for completed todos
export const $completedTodos = $todos.map(
todos => todos.filter(todo => todo.completed)
);
// Derived store for todo count
export const $todoCount = $todos.map(todos => todos.length);
```
### Combining Stores
Combine multiple stores into one:
```typescript
import { createStore, combine } from 'effector';
export const $user = createStore({ name: 'John' });
export const $settings = createStore({ theme: 'dark' });
// Combined store
export const $appState = combine({
user: $user,
settings: $settings
});
// Or with a custom mapper function
export const $userData = combine(
$user,
$settings,
(user, settings) => ({
...user,
theme: settings.theme
})
);
```
### Reset Events
Reset stores to their initial state:
```typescript
import { createEvent, createStore } from 'effector';
export const reset = createEvent();
export const $counter = createStore(0)
.reset(reset); // Reset to initial state (0)
// Call reset to restore initial state
reset();
```
## Best Practices
1. **Keep model.ts pure**: Avoid side effects in store updates.
2. **Use TypeScript**: Define types for all events, stores, and effects.
3. **Organize by domain**: Group related events, stores, and effects in domain folders.
4. **Use view-model.ts**: Create hooks that encapsulate Effector logic for React components.
5. **Keep UI components simple**: UI components should only consume data from hooks and call events.
6. **Test model.ts**: Write unit tests for your Effector logic.