Skip to content

docs: improve function bindings documentation with examples #16310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 118 additions & 9 deletions packages/svelte/src/reactivity/create-subscriber.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,78 @@ import { increment } from './utils.js';
import { DEV } from 'esm-env';

/**
* Returns a `subscribe` function that, if called in an effect (including expressions in the template),
* calls its `start` callback with an `update` function. Whenever `update` is called, the effect re-runs.
* Returns a `subscribe` function that bridges external, non-reactive changes
* to Svelte's reactivity system. It's ideal for integrating with browser APIs,
* WebSockets, or any event-based source outside of Svelte's control.
*
* If `start` returns a function, it will be called when the effect is destroyed.
* Call the returned `subscribe()` function inside a getter to make that getter
* reactive. When the external source changes, you call an `update` function,
* which in turn causes any effects that depend on the getter to re-run.
*
* If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
* are active, and the returned teardown function will only be called when all effects are destroyed.
* @param {(update: () => void) => (() => void) | void} start
* A callback that runs when the subscription is first activated by an effect.
* It receives an `update` function, which you should call to signal that
* the external data source has changed. The `start` callback can optionally
* return a `cleanup` function, which will be called when the last effect
* that depends on it is destroyed.
* @returns {() => void}
* A `subscribe` function that you call inside a getter to establish the
* reactive connection.
*
* @example
* ### The Generic Pattern
*
* It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery):
* This pattern shows how to create a reusable utility that encapsulates the
* external state and subscription logic.
*
* ```js
* import { createSubscriber } from 'svelte/reactivity';
*
* export function createReactiveExternalState() {
* let state = someInitialValue;
*
* const subscribe = createSubscriber((update) => {
* // Set up your external listener (DOM event, WebSocket, timer, etc.)
* const cleanup = setupListener(() => {
* state = newValue; // Update your state
* update(); // Call this to trigger Svelte reactivity
* });
*
* // Return cleanup function
* return () => cleanup();
* });
*
* return {
* get current() {
* subscribe(); // This "paints" the getter as reactive
* return state;
* }
* };
* }
* ```
*
* ### Implementation Details
*
* Internally, `createSubscriber` creates a hidden reactive `$state` variable
* that acts as a version number. Calling the `update` function increments this
* version. When the `subscribe` function is called within an effect, it reads
* this version number, creating a dependency. This mechanism ensures that
* getters become reactive to the external changes you signal.
*
* This approach is highly efficient:
* - **Lazy:** The `start` callback is only executed when the getter is first
* used inside an active effect.
* - **Automatic Cleanup:** The returned cleanup function is automatically
* called when the last subscribing effect is destroyed.
* - **Shared:** If multiple effects depend on the same getter, the `start`
* callback is still only called once.
*
* It's best understood with more examples.
*
* @example
* ### MediaQuery
*
* Here's a practical implementation of a reactive `MediaQuery` utility class.
*
* ```js
* import { createSubscriber } from 'svelte/reactivity';
Expand All @@ -39,12 +102,58 @@ import { DEV } from 'esm-env';
* get current() {
* this.#subscribe();
*
* // Return the current state of the query, whether or not we're in an effect
* // Return the current state, whether or not we're in an effect
* return this.#query.matches;
* }
* }
* ```
* @param {(update: () => void) => (() => void) | void} start
*
* @example
* ### Mouse Position
*
* This example creates a utility that reactively tracks mouse coordinates.
*
* ```js
* import { createSubscriber } from 'svelte/reactivity';
* import { on } from 'svelte/events';
*
* export function createMousePosition() {
* let x = 0;
* let y = 0;
*
* const subscribe = createSubscriber((update) => {
* const handleMouseMove = (event) => {
* x = event.clientX;
* y = event.clientY;
* update(); // Trigger reactivity
* };
*
* const off = on(window, 'mousemove', handleMouseMove);
* return () => off();
* });
*
* return {
* get x() {
* subscribe(); // Makes x reactive
* return x;
* },
* get y() {
* subscribe(); // Makes y reactive
* return y;
* }
* };
* }
* ```
*
* ### When to use `createSubscriber`
*
* - To synchronize Svelte's reactivity with external event sources like DOM
* events, `postMessage`, or WebSockets.
* - To create reactive wrappers around browser APIs (`matchMedia`,
* `IntersectionObserver`, etc.).
* - When you have a value that is read from an external source and you need
* components to update when that value changes. It is a more direct
* alternative to using `$state` and `$effect` for this specific purpose.
* @since 5.7.0
*/
export function createSubscriber(start) {
Expand Down Expand Up @@ -72,7 +181,7 @@ export function createSubscriber(start) {
tick().then(() => {
// Only count down after timeout, else we would reach 0 before our own render effect reruns,
// but reach 1 again when the tick callback of the prior teardown runs. That would mean we
// re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
// re-subscribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
subscribers -= 1;

if (subscribers === 0) {
Expand Down
124 changes: 117 additions & 7 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2318,15 +2318,78 @@ declare module 'svelte/reactivity' {
constructor(query: string, fallback?: boolean | undefined);
}
/**
* Returns a `subscribe` function that, if called in an effect (including expressions in the template),
* calls its `start` callback with an `update` function. Whenever `update` is called, the effect re-runs.
* Returns a `subscribe` function that bridges external, non-reactive changes
* to Svelte's reactivity system. It's ideal for integrating with browser APIs,
* WebSockets, or any event-based source outside of Svelte's control.
*
* Call the returned `subscribe()` function inside a getter to make that getter
* reactive. When the external source changes, you call an `update` function,
* which in turn causes any effects that depend on the getter to re-run.
*
* @param start
* A callback that runs when the subscription is first activated by an effect.
* It receives an `update` function, which you should call to signal that
* the external data source has changed. The `start` callback can optionally
* return a `cleanup` function, which will be called when the last effect
* that depends on it is destroyed.
* @returns
* A `subscribe` function that you call inside a getter to establish the
* reactive connection.
*
* @example
* ### The Generic Pattern
*
* This pattern shows how to create a reusable utility that encapsulates the
* external state and subscription logic.
*
* If `start` returns a function, it will be called when the effect is destroyed.
* ```js
* import { createSubscriber } from 'svelte/reactivity';
*
* export function createReactiveExternalState() {
* let state = someInitialValue;
*
* const subscribe = createSubscriber((update) => {
* // Set up your external listener (DOM event, WebSocket, timer, etc.)
* const cleanup = setupListener(() => {
* state = newValue; // Update your state
* update(); // Call this to trigger Svelte reactivity
* });
*
* // Return cleanup function
* return () => cleanup();
* });
*
* return {
* get current() {
* subscribe(); // This "paints" the getter as reactive
* return state;
* }
* };
* }
* ```
*
* ### Implementation Details
*
* If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
* are active, and the returned teardown function will only be called when all effects are destroyed.
* Internally, `createSubscriber` creates a hidden reactive `$state` variable
* that acts as a version number. Calling the `update` function increments this
* version. When the `subscribe` function is called within an effect, it reads
* this version number, creating a dependency. This mechanism ensures that
* getters become reactive to the external changes you signal.
*
* It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery):
* This approach is highly efficient:
* - **Lazy:** The `start` callback is only executed when the getter is first
* used inside an active effect.
* - **Automatic Cleanup:** The returned cleanup function is automatically
* called when the last subscribing effect is destroyed.
* - **Shared:** If multiple effects depend on the same getter, the `start`
* callback is still only called once.
*
* It's best understood with more examples.
*
* @example
* ### MediaQuery
*
* Here's a practical implementation of a reactive `MediaQuery` utility class.
*
* ```js
* import { createSubscriber } from 'svelte/reactivity';
Expand All @@ -2351,11 +2414,58 @@ declare module 'svelte/reactivity' {
* get current() {
* this.#subscribe();
*
* // Return the current state of the query, whether or not we're in an effect
* // Return the current state, whether or not we're in an effect
* return this.#query.matches;
* }
* }
* ```
*
* @example
* ### Mouse Position
*
* This example creates a utility that reactively tracks mouse coordinates.
*
* ```js
* import { createSubscriber } from 'svelte/reactivity';
* import { on } from 'svelte/events';
*
* export function createMousePosition() {
* let x = 0;
* let y = 0;
*
* const subscribe = createSubscriber((update) => {
* const handleMouseMove = (event) => {
* x = event.clientX;
* y = event.clientY;
* update(); // Trigger reactivity
* };
*
* const off = on(window, 'mousemove', handleMouseMove);
* return () => off();
* });
*
* return {
* get x() {
* subscribe(); // Makes x reactive
* return x;
* },
* get y() {
* subscribe(); // Makes y reactive
* return y;
* }
* };
* }
* ```
*
* ### When to use `createSubscriber`
*
* - To synchronize Svelte's reactivity with external event sources like DOM
* events, `postMessage`, or WebSockets.
* - To create reactive wrappers around browser APIs (`matchMedia`,
* `IntersectionObserver`, etc.).
* - When you have a value that is read from an external source and you need
* components to update when that value changes. It is a more direct
* alternative to using `$state` and `$effect` for this specific purpose.
* @since 5.7.0
*/
export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void;
Expand Down
Loading