213 lines
4.8 KiB
Markdown
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.
|