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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy