@@ -6,15 +6,78 @@ import { increment } from './utils.js';
6
6
import { DEV } from 'esm-env' ;
7
7
8
8
/**
9
- * Returns a `subscribe` function that, if called in an effect (including expressions in the template),
10
- * calls its `start` callback with an `update` function. Whenever `update` is called, the effect re-runs.
9
+ * Returns a `subscribe` function that bridges external, non-reactive changes
10
+ * to Svelte's reactivity system. It's ideal for integrating with browser APIs,
11
+ * WebSockets, or any event-based source outside of Svelte's control.
11
12
*
12
- * If `start` returns a function, it will be called when the effect is destroyed.
13
+ * Call the returned `subscribe()` function inside a getter to make that getter
14
+ * reactive. When the external source changes, you call an `update` function,
15
+ * which in turn causes any effects that depend on the getter to re-run.
13
16
*
14
- * If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
15
- * are active, and the returned teardown function will only be called when all effects are destroyed.
17
+ * @param {(update: () => void) => (() => void) | void } start
18
+ * A callback that runs when the subscription is first activated by an effect.
19
+ * It receives an `update` function, which you should call to signal that
20
+ * the external data source has changed. The `start` callback can optionally
21
+ * return a `cleanup` function, which will be called when the last effect
22
+ * that depends on it is destroyed.
23
+ * @returns {() => void }
24
+ * A `subscribe` function that you call inside a getter to establish the
25
+ * reactive connection.
26
+ *
27
+ * @example
28
+ * ### The Generic Pattern
16
29
*
17
- * It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery):
30
+ * This pattern shows how to create a reusable utility that encapsulates the
31
+ * external state and subscription logic.
32
+ *
33
+ * ```js
34
+ * import { createSubscriber } from 'svelte/reactivity';
35
+ *
36
+ * export function createReactiveExternalState() {
37
+ * let state = someInitialValue;
38
+ *
39
+ * const subscribe = createSubscriber((update) => {
40
+ * // Set up your external listener (DOM event, WebSocket, timer, etc.)
41
+ * const cleanup = setupListener(() => {
42
+ * state = newValue; // Update your state
43
+ * update(); // Call this to trigger Svelte reactivity
44
+ * });
45
+ *
46
+ * // Return cleanup function
47
+ * return () => cleanup();
48
+ * });
49
+ *
50
+ * return {
51
+ * get current() {
52
+ * subscribe(); // This "paints" the getter as reactive
53
+ * return state;
54
+ * }
55
+ * };
56
+ * }
57
+ * ```
58
+ *
59
+ * ### Implementation Details
60
+ *
61
+ * Internally, `createSubscriber` creates a hidden reactive `$state` variable
62
+ * that acts as a version number. Calling the `update` function increments this
63
+ * version. When the `subscribe` function is called within an effect, it reads
64
+ * this version number, creating a dependency. This mechanism ensures that
65
+ * getters become reactive to the external changes you signal.
66
+ *
67
+ * This approach is highly efficient:
68
+ * - **Lazy:** The `start` callback is only executed when the getter is first
69
+ * used inside an active effect.
70
+ * - **Automatic Cleanup:** The returned cleanup function is automatically
71
+ * called when the last subscribing effect is destroyed.
72
+ * - **Shared:** If multiple effects depend on the same getter, the `start`
73
+ * callback is still only called once.
74
+ *
75
+ * It's best understood with more examples.
76
+ *
77
+ * @example
78
+ * ### MediaQuery
79
+ *
80
+ * Here's a practical implementation of a reactive `MediaQuery` utility class.
18
81
*
19
82
* ```js
20
83
* import { createSubscriber } from 'svelte/reactivity';
@@ -39,12 +102,58 @@ import { DEV } from 'esm-env';
39
102
* get current() {
40
103
* this.#subscribe();
41
104
*
42
- * // Return the current state of the query , whether or not we're in an effect
105
+ * // Return the current state, whether or not we're in an effect
43
106
* return this.#query.matches;
44
107
* }
45
108
* }
46
109
* ```
47
- * @param {(update: () => void) => (() => void) | void } start
110
+ *
111
+ * @example
112
+ * ### Mouse Position
113
+ *
114
+ * This example creates a utility that reactively tracks mouse coordinates.
115
+ *
116
+ * ```js
117
+ * import { createSubscriber } from 'svelte/reactivity';
118
+ * import { on } from 'svelte/events';
119
+ *
120
+ * export function createMousePosition() {
121
+ * let x = 0;
122
+ * let y = 0;
123
+ *
124
+ * const subscribe = createSubscriber((update) => {
125
+ * const handleMouseMove = (event) => {
126
+ * x = event.clientX;
127
+ * y = event.clientY;
128
+ * update(); // Trigger reactivity
129
+ * };
130
+ *
131
+ * const off = on(window, 'mousemove', handleMouseMove);
132
+ * return () => off();
133
+ * });
134
+ *
135
+ * return {
136
+ * get x() {
137
+ * subscribe(); // Makes x reactive
138
+ * return x;
139
+ * },
140
+ * get y() {
141
+ * subscribe(); // Makes y reactive
142
+ * return y;
143
+ * }
144
+ * };
145
+ * }
146
+ * ```
147
+ *
148
+ * ### When to use `createSubscriber`
149
+ *
150
+ * - To synchronize Svelte's reactivity with external event sources like DOM
151
+ * events, `postMessage`, or WebSockets.
152
+ * - To create reactive wrappers around browser APIs (`matchMedia`,
153
+ * `IntersectionObserver`, etc.).
154
+ * - When you have a value that is read from an external source and you need
155
+ * components to update when that value changes. It is a more direct
156
+ * alternative to using `$state` and `$effect` for this specific purpose.
48
157
* @since 5.7.0
49
158
*/
50
159
export function createSubscriber ( start ) {
@@ -72,7 +181,7 @@ export function createSubscriber(start) {
72
181
tick ( ) . then ( ( ) => {
73
182
// Only count down after timeout, else we would reach 0 before our own render effect reruns,
74
183
// but reach 1 again when the tick callback of the prior teardown runs. That would mean we
75
- // re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
184
+ // re-subscribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
76
185
subscribers -= 1 ;
77
186
78
187
if ( subscribers === 0 ) {
0 commit comments