@eficy/reactive
Version:
A modern annotation-based reactive state management library with MobX-compatible API, powered by @preact/signals-core
322 lines (238 loc) โข 8.4 kB
Markdown
# /reactive
A modern annotation-based reactive state management library with **MobX-compatible API**, powered by `/signals-core` for high-performance reactivity.
## ๐ Key Features
- **๐ฏ Signal-Based**: High-performance reactivity powered by `/signals-core`
- **๐ MobX-Compatible API**: Familiar annotations and patterns from MobX
- **โก Automatic Batching**: Actions automatically batch state updates
- **๐ฆ Type-Safe**: Full TypeScript support with excellent type inference
- **๐ No Proxy**: Better compatibility without Proxy dependency
- **๐จ Flexible Design**: Supports arrays, objects and complex state structures
- **๐งช Well Tested**: >90% test coverage with comprehensive unit tests
- **๐ฏ Decorator Support**: TypeScript decorators for class-based reactive programming
## ๐ฆ Installation
```bash
npm install /reactive reflect-metadata
# or
yarn add /reactive reflect-metadata
# or
pnpm add /reactive reflect-metadata
```
**Note**: `reflect-metadata` is required for decorator support.
## ๐ Quick Start
### Function-based API (Recommended)
```typescript
import { observable, computed, effect, action } from '@eficy/reactive';
// Create reactive state
const userStore = observable({
firstName: 'John',
lastName: 'Doe',
age: 25
});
// Create computed values
const fullName = computed(() => `${userStore.get('firstName')} ${userStore.get('lastName')}`);
const isAdult = computed(() => userStore.get('age') >= 18);
// Auto-run effects
effect(() => {
console.log(`User: ${fullName()}, Adult: ${isAdult()}`);
});
// Create actions (MobX-style)
const updateUser = action((first: string, last: string, age: number) => {
userStore.set('firstName', first);
userStore.set('lastName', last);
userStore.set('age', age);
});
// Trigger updates
updateUser('Jane', 'Smith', 30); // Output: User: Jane Smith, Adult: true
```
### Decorator-based API (Class Style)
For TypeScript projects with decorator support, you can use the class-based API:
```typescript
import 'reflect-metadata';
import { Observable, Computed, Action, makeObservable, ObservableClass } from '@eficy/reactive/annotation';
// Option 1: Manual makeObservable
class UserStore {
firstName = 'John';
lastName = 'Doe';
age = 25;
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
get isAdult(): boolean {
return this.age >= 18;
}
updateUser(first: string, last: string, age: number) {
this.firstName = first;
this.lastName = last;
this.age = age;
}
constructor() {
makeObservable(this);
}
}
// Option 2: ObservableClass base class (auto makeObservable)
class UserStore extends ObservableClass {
firstName = 'John';
lastName = 'Doe';
age = 25;
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
get isAdult(): boolean {
return this.age >= 18;
}
updateUser(first: string, last: string, age: number) {
this.firstName = first;
this.lastName = last;
this.age = age;
}
}
// Usage
const store = new UserStore();
effect(() => {
console.log(`User: ${store.fullName}, Adult: ${store.isAdult}`);
});
store.updateUser('Jane', 'Smith', 30);
```
### Decorator Configuration
To use decorators, ensure your TypeScript configuration supports them:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
If using Vite or other build tools, you may need additional configuration for decorator support.
### Observable Arrays (MobX-Compatible)
```typescript
import { observableArray, action } from '@eficy/reactive';
const todos = observableArray<string>(['Learn', 'Work']);
// Array operations automatically trigger updates
const addTodo = action((todo: string) => {
todos.push(todo);
});
const removeTodo = action((index: number) => {
todos.splice(index, 1);
});
// Watch array changes
effect(() => {
console.log('Todos:', todos.toArray());
console.log('Count:', todos.length);
});
addTodo('Exercise'); // Automatically triggers updates
```
### Observable Objects (MobX-Compatible)
```typescript
import { observableObject } from '@eficy/reactive';
const user = observableObject({
name: 'John',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true
}
});
// Reactive updates
effect(() => {
console.log(`${user.get('name')} (${user.get('email')})`);
});
// Update nested properties
user.set('name', 'Jane');
user.update({ email: 'jane@example.com' });
```
## ๐ API Reference
### Core Functions
- **`signal(initialValue)`** - Create a reactive signal
- **`computed(fn)`** - Create a computed value that automatically updates
- **`effect(fn)`** - Create a side effect that runs when dependencies change
- **`action(fn)`** - Wrap function to batch updates and improve performance
- **`batch(fn)`** - Manually batch multiple updates
- **`watch(signal, callback)`** - Watch for signal changes
### Observable Creation
- **`observable(value)`** - Auto-detect type and create appropriate observable
- **`observable.box(value)`** - Create observable primitive (signal)
- **`observable.object(obj)`** - Create observable object
- **`observable.array(arr)`** - Create observable array
- **`observable.map(map)`** - Create observable Map
- **`observable.set(set)`** - Create observable Set
### Decorators (from '@eficy/reactive/annotation')
- **``** - Mark class property as observable
- **``** - Mark property as observable with initial value
- **``** - Mark getter as computed property
- **``** - Mark method as action
- **``** - Mark method as action with custom name
- **`makeObservable(instance)`** - Apply decorators to class instance
- **`ObservableClass`** - Base class that auto-applies makeObservable
### Collections
- **`observableArray<T>(items?)`** - Reactive array with MobX-compatible API
- **`observableObject<T>(obj)`** - Reactive object with get/set methods
- **`observableMap<K,V>(entries?)`** - Reactive Map
- **`observableSet<T>(values?)`** - Reactive Set
## ๐ฏ Migration from MobX
This library is designed to be largely compatible with MobX patterns:
```typescript
// MobX style
import { Observable, Computed, Action, makeObservable } from 'mobx';
// @eficy/reactive style (very similar!)
import { Observable, Computed, Action, makeObservable } from '@eficy/reactive/annotation';
```
Key differences:
1. Uses `/signals-core` instead of Proxy-based reactivity
2. Requires `reflect-metadata` for decorators
3. Function-based API available as alternative to class-based
4. Some advanced MobX features may not be available
## โก Performance Tips
1. **Use actions for batching**: Wrap multiple state updates in `action()` for better performance
2. **Computed caching**: Computed values are automatically cached and only recalculate when dependencies change
3. **Selective observation**: Only observe the data you actually need in components
4. **Avoid creating observables in render**: Create observables outside render functions
## ๐งช Testing
```typescript
import { signal, effect } from '@eficy/reactive';
// Test reactive behavior
const count = signal(0);
let effectRuns = 0;
effect(() => {
effectRuns++;
count(); // Read signal to create dependency
});
expect(effectRuns).toBe(1);
count(5);
expect(effectRuns).toBe(2);
```
## ๐ TypeScript Support
This library is written in TypeScript and provides excellent type inference:
```typescript
// Types are automatically inferred
const user = observable({
name: 'John', // string
age: 25, // number
active: true // boolean
});
// TypeScript knows the return type
const greeting = computed(() => {
return `Hello, ${user.get('name')}!`; // string
});
```
## ๐ Ecosystem
- **[/reactive-react](../reactive-react)** - React bindings for /reactive
- **[/core](../core)** - UI framework using /reactive
## ๐ License
MIT License - see LICENSE file for details.
## ๐ค Contributing
Contributions welcome! Please read our contributing guidelines and submit pull requests to our repository.
---
**Made with โค๏ธ by the Eficy team**