Skip to content

Commit b3833be

Browse files
authored
docs: Move non-API signals docs out of the API docs section (#1380)
1 parent aa6e801 commit b3833be

File tree

2 files changed

+244
-244
lines changed

2 files changed

+244
-244
lines changed

content/en/guide/v10/signals.md

Lines changed: 122 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,128 @@ console.log(counter.count.value); // 6
543543

544544
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.
545545

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+
const TodoItemModel = createModel((text) => {
559+
const completed = signal(false);
560+
561+
return {
562+
text,
563+
completed,
564+
toggle() {
565+
completed.value = !completed.value;
566+
}
567+
};
568+
});
569+
570+
const TodoListModel = createModel(() => {
571+
const items = signal([]);
572+
573+
return {
574+
items,
575+
addTodo(text) {
576+
const todo = new TodoItemModel(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+
const todoList = new TodoListModel();
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:
599+
600+
```ts
601+
import { signal, computed, createModel, ReadonlySignal } from '@preact/signals';
602+
603+
interface Counter {
604+
count: ReadonlySignal<number>;
605+
doubled: ReadonlySignal<number>;
606+
increment(): void;
607+
decrement(): void;
608+
}
609+
610+
const CounterModel = createModel<Counter>(() => {
611+
const count = signal(0);
612+
const doubled = computed(() => count.value * 2);
613+
614+
return {
615+
count,
616+
doubled,
617+
increment() {
618+
count.value++;
619+
},
620+
decrement() {
621+
count.value--;
622+
}
623+
};
624+
});
625+
626+
const counter = new CounterModel();
627+
counter.increment(); // OK
628+
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+
const WebSocketModel = createModel((url) => {
637+
const messages = signal([]);
638+
const ws = new WebSocket(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
645+
effect(() => {
646+
return () => {
647+
ws.close();
648+
};
649+
});
650+
651+
return {
652+
messages,
653+
send(message) {
654+
ws.send(message);
655+
}
656+
};
657+
});
658+
659+
const chat = new WebSocketModel('wss://example.com/chat');
660+
chat.send('Hello!');
661+
662+
// Closes the WebSocket connection on dispose
663+
chat[Symbol.dispose]();
664+
```
665+
666+
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+
546668
## API
547669

548670
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.
@@ -686,54 +808,6 @@ console.log(counter.doubled.value); // 12
686808
counter[Symbol.dispose]();
687809
```
688810

689-
#### Key Features
690-
691-
- **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-
const TodoItemModel = createModel((text) => {
702-
const completed = signal(false);
703-
704-
return {
705-
text,
706-
completed,
707-
toggle() {
708-
completed.value = !completed.value;
709-
}
710-
};
711-
});
712-
713-
const TodoListModel = createModel(() => {
714-
const items = signal([]);
715-
716-
return {
717-
items,
718-
addTodo(text) {
719-
const todo = new TodoItemModel(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-
const todoList = new TodoListModel();
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-
737811
### action(fn)
738812

739813
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 }) {
798872
}
799873
```
800874

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:
806-
807-
```ts
808-
import { signal, computed, createModel, ReadonlySignal } from '@preact/signals';
809-
810-
interface Counter {
811-
count: ReadonlySignal<number>;
812-
doubled: ReadonlySignal<number>;
813-
increment(): void;
814-
decrement(): void;
815-
}
816-
817-
const CounterModel = createModel<Counter>(() => {
818-
const count = signal(0);
819-
const doubled = computed(() => count.value * 2);
820-
821-
return {
822-
count,
823-
doubled,
824-
increment() {
825-
count.value++;
826-
},
827-
decrement() {
828-
count.value--;
829-
}
830-
};
831-
});
832-
833-
const counter = new CounterModel();
834-
counter.increment(); // OK
835-
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-
const WebSocketModel = createModel((url) => {
844-
const messages = signal([]);
845-
const ws = new WebSocket(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
852-
effect(() => {
853-
return () => {
854-
ws.close();
855-
};
856-
});
857-
858-
return {
859-
messages,
860-
send(message) {
861-
ws.send(message);
862-
}
863-
};
864-
});
865-
866-
const chat = new WebSocketModel('wss://example.com/chat');
867-
chat.send('Hello!');
868-
869-
// Closes the WebSocket connection on dispose
870-
chat[Symbol.dispose]();
871-
```
872-
873-
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-
875875
## Utility Components and Hooks
876876

877877
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

Comments
 (0)