You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For more details on how to use models in your components and the full API reference, see the [Model APIs](#createmodelfactory) in the API section below.
545
545
546
+
### Key Features
547
+
548
+
-**Factory arguments**: Factory functions can accept arguments for initialization, making models reusable with different configurations.
549
+
-**Automatic batching**: All methods returned from the factory are automatically wrapped as actions, meaning state updates within them are batched and untracked.
550
+
-**Automatic effect cleanup**: Effects created during model construction are captured and automatically disposed when the model is disposed via `Symbol.dispose`.
551
+
-**Composable models**: Models compose naturally - effects from nested models are captured by the parent and disposed together when the parent is disposed.
552
+
553
+
### Model Composition
554
+
555
+
Models can be nested within other models. When a parent model is disposed, all effects from nested models are automatically cleaned up:
556
+
557
+
```js
558
+
constTodoItemModel=createModel((text) => {
559
+
constcompleted=signal(false);
560
+
561
+
return {
562
+
text,
563
+
completed,
564
+
toggle() {
565
+
completed.value=!completed.value;
566
+
}
567
+
};
568
+
});
569
+
570
+
constTodoListModel=createModel(() => {
571
+
constitems=signal([]);
572
+
573
+
return {
574
+
items,
575
+
addTodo(text) {
576
+
consttodo=newTodoItemModel(text);
577
+
items.value= [...items.value, todo];
578
+
},
579
+
removeTodo(todo) {
580
+
items.value=items.value.filter(t=> t !== todo);
581
+
todo[Symbol.dispose]();
582
+
}
583
+
};
584
+
});
585
+
586
+
consttodoList=newTodoListModel();
587
+
todoList.addTodo('Buy groceries');
588
+
todoList.addTodo('Walk the dog');
589
+
590
+
// Disposing the parent also cleans up all nested model effects
591
+
todoList[Symbol.dispose]();
592
+
```
593
+
594
+
### Recommended Patterns
595
+
596
+
#### Explicit ReadonlySignal Pattern
597
+
598
+
For better encapsulation, declare your model interface explicitly and use `ReadonlySignal` for signals that should only be modified through actions:
counter.count.value=10; // TypeScript error: Cannot assign to 'value'
629
+
```
630
+
631
+
#### Custom Dispose Logic
632
+
633
+
If your model needs custom cleanup logic that isn't related to signals (such as closing WebSocket connections), use an effect with no dependencies that returns a cleanup function:
634
+
635
+
```js
636
+
constWebSocketModel=createModel((url) => {
637
+
constmessages=signal([]);
638
+
constws=newWebSocket(url);
639
+
640
+
ws.onmessage= (e) => {
641
+
messages.value= [...messages.value, e.data];
642
+
};
643
+
644
+
// This effect runs once; its cleanup runs on dispose
This pattern mirrors `useEffect(() => { return cleanup }, [])` in React and ensures that cleanup happens automatically when models are composed together - parent models don't need to know about the dispose functions of nested models.
667
+
546
668
## API
547
669
548
670
This section is an overview of the signals API. It's aimed to be a quick reference for folks who already know how to use signals and need a reminder of what's available.
-**Factory arguments**: Factory functions can accept arguments for initialization, making models reusable with different configurations.
692
-
-**Automatic batching**: All methods returned from the factory are automatically wrapped as actions, meaning state updates within them are batched and untracked.
693
-
-**Automatic effect cleanup**: Effects created during model construction are captured and automatically disposed when the model is disposed via `Symbol.dispose`.
694
-
-**Composable models**: Models compose naturally - effects from nested models are captured by the parent and disposed together when the parent is disposed.
695
-
696
-
#### Model Composition
697
-
698
-
Models can be nested within other models. When a parent model is disposed, all effects from nested models are automatically cleaned up:
699
-
700
-
```js
701
-
constTodoItemModel=createModel((text) => {
702
-
constcompleted=signal(false);
703
-
704
-
return {
705
-
text,
706
-
completed,
707
-
toggle() {
708
-
completed.value=!completed.value;
709
-
}
710
-
};
711
-
});
712
-
713
-
constTodoListModel=createModel(() => {
714
-
constitems=signal([]);
715
-
716
-
return {
717
-
items,
718
-
addTodo(text) {
719
-
consttodo=newTodoItemModel(text);
720
-
items.value= [...items.value, todo];
721
-
},
722
-
removeTodo(todo) {
723
-
items.value=items.value.filter(t=> t !== todo);
724
-
todo[Symbol.dispose]();
725
-
}
726
-
};
727
-
});
728
-
729
-
consttodoList=newTodoListModel();
730
-
todoList.addTodo('Buy groceries');
731
-
todoList.addTodo('Walk the dog');
732
-
733
-
// Disposing the parent also cleans up all nested model effects
734
-
todoList[Symbol.dispose]();
735
-
```
736
-
737
811
### action(fn)
738
812
739
813
The `action(fn)` function wraps a function to run in a batched and untracked context. This is useful when you need to create standalone actions outside of a model:
@@ -798,80 +872,6 @@ function Counter({ initialValue }) {
798
872
}
799
873
```
800
874
801
-
### Recommended Patterns
802
-
803
-
#### Explicit ReadonlySignal Pattern
804
-
805
-
For better encapsulation, declare your model interface explicitly and use `ReadonlySignal` for signals that should only be modified through actions:
counter.count.value=10; // TypeScript error: Cannot assign to 'value'
836
-
```
837
-
838
-
#### Custom Dispose Logic
839
-
840
-
If your model needs custom cleanup logic that isn't related to signals (such as closing WebSocket connections), use an effect with no dependencies that returns a cleanup function:
841
-
842
-
```js
843
-
constWebSocketModel=createModel((url) => {
844
-
constmessages=signal([]);
845
-
constws=newWebSocket(url);
846
-
847
-
ws.onmessage= (e) => {
848
-
messages.value= [...messages.value, e.data];
849
-
};
850
-
851
-
// This effect runs once; its cleanup runs on dispose
This pattern mirrors `useEffect(() => { return cleanup }, [])` in React and ensures that cleanup happens automatically when models are composed together - parent models don't need to know about the dispose functions of nested models.
874
-
875
875
## Utility Components and Hooks
876
876
877
877
As of v2.1.0, the `@preact/signals/utils` package provides additional utility components and hooks to make working with signals even easier.
0 commit comments