Skip to content

Commit 15fea4c

Browse files
committed
docs: enhance createSubscriber documentation with comprehensive examples
- Improved documentation clarity with natural language explanations - Added clear generic pattern example showing the subscription model - Enhanced MediaQuery and mouse position examples with proper formatting - Explained internal mechanism and key characteristics - Added guidance on when to use createSubscriber
1 parent 432763a commit 15fea4c

File tree

2 files changed

+235
-16
lines changed

2 files changed

+235
-16
lines changed

packages/svelte/src/reactivity/create-subscriber.js

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,78 @@ import { increment } from './utils.js';
66
import { DEV } from 'esm-env';
77

88
/**
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.
1112
*
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.
1316
*
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
1629
*
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.
1881
*
1982
* ```js
2083
* import { createSubscriber } from 'svelte/reactivity';
@@ -39,12 +102,58 @@ import { DEV } from 'esm-env';
39102
* get current() {
40103
* this.#subscribe();
41104
*
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
43106
* return this.#query.matches;
44107
* }
45108
* }
46109
* ```
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.
48157
* @since 5.7.0
49158
*/
50159
export function createSubscriber(start) {
@@ -72,7 +181,7 @@ export function createSubscriber(start) {
72181
tick().then(() => {
73182
// Only count down after timeout, else we would reach 0 before our own render effect reruns,
74183
// 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.
76185
subscribers -= 1;
77186

78187
if (subscribers === 0) {

packages/svelte/types/index.d.ts

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,15 +2318,78 @@ declare module 'svelte/reactivity' {
23182318
constructor(query: string, fallback?: boolean | undefined);
23192319
}
23202320
/**
2321-
* Returns a `subscribe` function that, if called in an effect (including expressions in the template),
2322-
* calls its `start` callback with an `update` function. Whenever `update` is called, the effect re-runs.
2321+
* Returns a `subscribe` function that bridges external, non-reactive changes
2322+
* to Svelte's reactivity system. It's ideal for integrating with browser APIs,
2323+
* WebSockets, or any event-based source outside of Svelte's control.
2324+
*
2325+
* Call the returned `subscribe()` function inside a getter to make that getter
2326+
* reactive. When the external source changes, you call an `update` function,
2327+
* which in turn causes any effects that depend on the getter to re-run.
2328+
*
2329+
* @param start
2330+
* A callback that runs when the subscription is first activated by an effect.
2331+
* It receives an `update` function, which you should call to signal that
2332+
* the external data source has changed. The `start` callback can optionally
2333+
* return a `cleanup` function, which will be called when the last effect
2334+
* that depends on it is destroyed.
2335+
* @returns
2336+
* A `subscribe` function that you call inside a getter to establish the
2337+
* reactive connection.
2338+
*
2339+
* @example
2340+
* ### The Generic Pattern
2341+
*
2342+
* This pattern shows how to create a reusable utility that encapsulates the
2343+
* external state and subscription logic.
23232344
*
2324-
* If `start` returns a function, it will be called when the effect is destroyed.
2345+
* ```js
2346+
* import { createSubscriber } from 'svelte/reactivity';
2347+
*
2348+
* export function createReactiveExternalState() {
2349+
* let state = someInitialValue;
2350+
*
2351+
* const subscribe = createSubscriber((update) => {
2352+
* // Set up your external listener (DOM event, WebSocket, timer, etc.)
2353+
* const cleanup = setupListener(() => {
2354+
* state = newValue; // Update your state
2355+
* update(); // Call this to trigger Svelte reactivity
2356+
* });
2357+
*
2358+
* // Return cleanup function
2359+
* return () => cleanup();
2360+
* });
2361+
*
2362+
* return {
2363+
* get current() {
2364+
* subscribe(); // This "paints" the getter as reactive
2365+
* return state;
2366+
* }
2367+
* };
2368+
* }
2369+
* ```
2370+
*
2371+
* ### Implementation Details
23252372
*
2326-
* If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
2327-
* are active, and the returned teardown function will only be called when all effects are destroyed.
2373+
* Internally, `createSubscriber` creates a hidden reactive `$state` variable
2374+
* that acts as a version number. Calling the `update` function increments this
2375+
* version. When the `subscribe` function is called within an effect, it reads
2376+
* this version number, creating a dependency. This mechanism ensures that
2377+
* getters become reactive to the external changes you signal.
23282378
*
2329-
* It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery):
2379+
* This approach is highly efficient:
2380+
* - **Lazy:** The `start` callback is only executed when the getter is first
2381+
* used inside an active effect.
2382+
* - **Automatic Cleanup:** The returned cleanup function is automatically
2383+
* called when the last subscribing effect is destroyed.
2384+
* - **Shared:** If multiple effects depend on the same getter, the `start`
2385+
* callback is still only called once.
2386+
*
2387+
* It's best understood with more examples.
2388+
*
2389+
* @example
2390+
* ### MediaQuery
2391+
*
2392+
* Here's a practical implementation of a reactive `MediaQuery` utility class.
23302393
*
23312394
* ```js
23322395
* import { createSubscriber } from 'svelte/reactivity';
@@ -2351,11 +2414,58 @@ declare module 'svelte/reactivity' {
23512414
* get current() {
23522415
* this.#subscribe();
23532416
*
2354-
* // Return the current state of the query, whether or not we're in an effect
2417+
* // Return the current state, whether or not we're in an effect
23552418
* return this.#query.matches;
23562419
* }
23572420
* }
23582421
* ```
2422+
*
2423+
* @example
2424+
* ### Mouse Position
2425+
*
2426+
* This example creates a utility that reactively tracks mouse coordinates.
2427+
*
2428+
* ```js
2429+
* import { createSubscriber } from 'svelte/reactivity';
2430+
* import { on } from 'svelte/events';
2431+
*
2432+
* export function createMousePosition() {
2433+
* let x = 0;
2434+
* let y = 0;
2435+
*
2436+
* const subscribe = createSubscriber((update) => {
2437+
* const handleMouseMove = (event) => {
2438+
* x = event.clientX;
2439+
* y = event.clientY;
2440+
* update(); // Trigger reactivity
2441+
* };
2442+
*
2443+
* const off = on(window, 'mousemove', handleMouseMove);
2444+
* return () => off();
2445+
* });
2446+
*
2447+
* return {
2448+
* get x() {
2449+
* subscribe(); // Makes x reactive
2450+
* return x;
2451+
* },
2452+
* get y() {
2453+
* subscribe(); // Makes y reactive
2454+
* return y;
2455+
* }
2456+
* };
2457+
* }
2458+
* ```
2459+
*
2460+
* ### When to use `createSubscriber`
2461+
*
2462+
* - To synchronize Svelte's reactivity with external event sources like DOM
2463+
* events, `postMessage`, or WebSockets.
2464+
* - To create reactive wrappers around browser APIs (`matchMedia`,
2465+
* `IntersectionObserver`, etc.).
2466+
* - When you have a value that is read from an external source and you need
2467+
* components to update when that value changes. It is a more direct
2468+
* alternative to using `$state` and `$effect` for this specific purpose.
23592469
* @since 5.7.0
23602470
*/
23612471
export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void;

0 commit comments

Comments
 (0)