@@ -41,7 +41,7 @@ Can be written as
4141``` tsx
4242function Box() {
4343 const [counter, setCounter] = createState (0 )
44- const label = createComputed ((get ) => ` clicked ${get ( counter )} times ` )
44+ const label = createComputed (() => ` clicked ${counter ( )} times ` )
4545
4646 function onClicked() {
4747 setCounter ((c ) => c + 1 )
@@ -200,8 +200,8 @@ function MyWidget() {
200200### Bindings
201201
202202Properties can be set as a static value. Alternatively, they can be passed an
203- [ Accessor] ( ./jsx#accessor ) , in which case whenever its value changes, it will be
204- reflected on the widget.
203+ [ Accessor] ( #state-management ) , in which case whenever its value changes, it will
204+ be reflected on the widget.
205205
206206``` tsx
207207const [revealed, setRevealed] = createState (false )
@@ -366,6 +366,31 @@ function MyWidget({ label, onClicked }: MyWidgetProps) {
366366}
367367```
368368
369+ > [ !TIP]
370+ >
371+ > To make reusable function components more convenient to use, you should
372+ > annotate props as either static or dynamic and handle both cases as if it was
373+ > dynamic.
374+ >
375+ > ``` ts
376+ > type $ <T > = T | Accessor <T >
377+ > const $ = <T >(value : $ <T >): Accessor <T > =>
378+ > value instanceof Accessor ? value : new Accessor (() => value )
379+ > ` ` `
380+
381+ ` ` ` tsx
382+ function Counter(props : {
383+ count? : $ <number >
384+ label? : $ <string >
385+ onClicked? : () => void
386+ }) {
387+ const count = $ (props .count )((v ) => v ?? 0 )
388+ const label = $ (props .label )((v ) => v ?? ` Fallback label ${count ()} ` )
389+
390+ return <Gtk .Button label = {label} onClicked = {props .onClicked} />
391+ }
392+ ```
393+
369394## Control flow
370395
371396### Dynamic rendering
@@ -385,8 +410,7 @@ return (
385410> [!TIP]
386411>
387412> In a lot of cases, it is better to always render the component and set its
388- > ` visible ` property instead. This is because ` <With> ` will destroy/recreate the
389- > widget each time the passed ` value ` changes.
413+ > ` visible ` property instead.
390414
391415> [!WARNING]
392416>
@@ -434,16 +458,30 @@ removing.
434458
435459## State management
436460
437- There is a single primitive called ` Accessor ` , which is a read-only signal.
461+ There is a single primitive called ` Accessor ` , which is a read-only reactive
462+ value. It is the base of Gnim's reactive system. They are essentially functions
463+ that let you read a value and track it in reactive scopes so that when they
464+ change the reader is notified.
438465
439466` ` ` ts
440- export interface Accessor <T > {
441- get (): T
442- subscribe( callback : () => void ) : () => void
443- < R = T >( transform : ( value : T ) => R ) : Accessor < R >
467+ interface Accessor <T > {
468+ (): T
469+ peek() : T
470+ subscribe( callback : Callback ) : DisposeFn
444471}
472+ ` ` `
473+
474+ There are two ways to read the current value:
475+
476+ - ` (): T ` : which returns the current value and tracks it as a dependency in
477+ reactive scopes
478+ - ` peek (): T ` which returns the current value **without** tracking it as a
479+ dependency
445480
446- let accessor: Accessor <any >
481+ To subscribe for value changes you can use the ` subscribe ` method.
482+
483+ ` ` ` ts
484+ const accessor: Accessor <any >
447485
448486const unsubscribe = accessor .subscribe (() => {
449487 console .log (" value of accessor changed to" , accessor .get ())
@@ -452,9 +490,14 @@ const unsubscribe = accessor.subscribe(() => {
452490unsubscribe ()
453491` ` `
454492
493+ > [!WARNING]
494+ >
495+ > The subscribe method is not scope aware. Do not forget to clean them up when
496+ > no longer needed. Alternatively, use an [ ` effect ` ](#createeffect) instead.
497+
455498### ` createState `
456499
457- Creates a writable signal .
500+ Creates a writable reactive value .
458501
459502` ` ` ts
460503function createState<T >(init : T ): [Accessor <T >, Setter <T >]
@@ -470,51 +513,60 @@ setValue(2)
470513setValue((prev) => prev + 1)
471514` ` `
472515
473- ### ` createComputed `
516+ > [! IMPORTANT ]
517+ >
518+ > Effects and computations are only triggered when the value changes .
474519
475- Creates a computed signal from a producer function that tracks its dependencies.
520+ By default , equality between the previous and new value is checked with
521+ [Object .is ](https :// developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
522+ and so this would not trigger an update :
476523
477524` ` ` ts
478- export function createComputed<T>(
479- producer: (track: <V>(signal: Accessor<V>) => V) => T,
480- ): Accessor<T>
525+ const [object, setObject] = createState({})
526+
527+ // this does NOT trigger an update by default
528+ setObject((obj) => {
529+ obj.field = "mutated"
530+ return obj
531+ })
481532` ` `
482533
483- Example :
534+ You can pass in a custom ` equals ` function to customize this behavior :
484535
485536```ts
486- let a: Accessor<number>
487- let b: Accessor<number>
488-
489- const c = createComputed((get) => get(a) + get(b))
537+ const [value, setValue] = createState(" initial value" , {
538+ equals : (prev , next ): boolean = > {
539+ return prev != next
540+ },
541+ })
490542```
491543
492- Alternatively , you can specify a list of dependencies , in which case values are
493- passed to an optional transform function .
544+ ### `createComputed`
545+
546+ Create a computed value which tracks dependencies and invalidates the value
547+ whenever they change. The result is cached and is only computed on access.
494548
495549```ts
496- function createComputed<
497- Deps extends Array<Accessor<any>>,
498- Values extends { [K in keyof Deps]: Accessed<Deps[K]> },
499- >(deps: Deps, transform: (...values: Values) => V): Accessor<V>
550+ function createComputed<T >(compute : () => T ): Accessor <T >
500551```
501552
502553Example:
503554
504555```ts
505- let a: Accessor<string >
506- let b: Accessor<string >
556+ let a: Accessor<number >
557+ let b: Accessor<number >
507558
508- const c = createComputed([a, b], (a, b ) => ` $ { a } + $ { b } ` )
559+ const c: Accessor< number > = createComputed(( ) => a () + b () )
509560```
510561
511562> [!TIP]
512563>
513- > There is a shorthand for single dependency computed signals .
564+ > There is a shorthand for computed values .
514565>
515566> ```ts
516567> let a: Accessor<string >
517- > const b: Accessor<string> = a((v) => ` transformed $ {v }` )
568+ > const b = createComputed(() => ` transformed ${a ()} ` )
569+ > const b = a((v ) => ` transformed ${v } ` ) // alias for the above line
518570 > ```
519571
520572### `createBinding`
@@ -535,13 +587,57 @@ const styleManager = Adw.StyleManager.get_default()
535587const style = createBinding(styleManager , " colorScheme" )
536588```
537589
590+ It also supports nested bindings.
591+
592+ ```ts
593+ interface Outer extends GObject.Object {
594+ nested : Inner | null
595+ }
596+
597+ interface Inner extends GObject .Object {
598+ field: string
599+ }
600+
601+ const value : Accessor < string | null > = createBinding (outer , " nested" , " field" )
602+ ` ` `
603+
604+ ### ` createEffect `
605+
606+ Schedule a function to run after the current Scope created with
607+ [ ` createRoot ` ](#createroot) returns, tracking dependencies and re-running the
608+ function whenever they change.
609+
610+ ` ` ` ts
611+ function createEffect(fn : () => void ): void
612+ ```
613+
614+ Example:
615+
616+ ```ts
617+ const count: Accessor<number >
618+
619+ createEffect(() => {
620+ console .log (count ()) // reruns whenever count changes
621+ })
622+
623+ createEffect (() => {
624+ console .log (count .peek ()) // only runs once
625+ })
626+ ` ` `
627+
628+ > [!CAUTION]
629+ >
630+ > Effects are a common pitfall for beginners to understand when to use and when
631+ > not to use them. You can read about
632+ > [when it is discouraged and their alternatives](./tutorial/gnim.md#when-not-to-use-an-effect).
633+
538634### ` createConnection `
539635
540636` ` ` ts
541637function createConnection<
542638 T ,
543639 O extends GObject .Object ,
544- S extends keyof O1 ["$signals"],
640+ S extends keyof O [" $signals" ],
545641>(
546642 init : T ,
547643 handler : [
@@ -562,7 +658,7 @@ arguments passed by the signal and the current value as the last parameter.
562658Example :
563659
564660` ` ` ts
565- const value = createConnection(
661+ const value: Accessor<string> = createConnection(
566662 "initial value",
567663 [obj1, "notify", (pspec, currentValue) => currentValue + pspec.name],
568664 [obj2, "sig-name", (sigArg1, sigArg2, currentValue) => "str"],
@@ -574,6 +670,36 @@ const value = createConnection(
574670> The connection will only get attached when the first subscriber appears , and
575671> is dropped when the last one disappears .
576672
673+ ### ` createMemo `
674+
675+ Create a derived reactive value which tracks its dependencies and re - runs the
676+ computation whenever a dependency changes . The resulting ` Accessor ` will only
677+ notify subscribers when the computed value has changed .
678+
679+ ` ` ` ts
680+ function createMemo<T>(compute: () => T): Accessor<T>
681+ ` ` `
682+
683+ It is useful to memoize values that are dependencies of expensive computations .
684+
685+ Example :
686+
687+ ` ` ` ts
688+ const value = createBinding(gobject, "field")
689+
690+ createEffect(() => {
691+ console.log("effect1", value())
692+ })
693+
694+ const memoValue = createMemo(() => value())
695+
696+ createEffect(() => {
697+ console.log("effect2", memoValue())
698+ })
699+
700+ value.notify("field") // triggers effect1 but not effect2
701+ ` ` `
702+
577703### ` createSettings `
578704
579705Wraps a ` Gio.Settings ` into a collection of setters and accessors .
@@ -627,15 +753,15 @@ const counter = createExternal(0, (set) => {
627753
628754## Scopes and Life cycle
629755
630- A scope is essentially a global object which holds cleanup functions and context
631- values .
756+ A [ scope ](. / tutorial / gnim . md # scopes ) is essentially a global object which holds
757+ cleanup functions and context values .
632758
633759` ` ` js
634760let scope = new Scope()
635761
636762// Inside this function, synchronously executed code will have access
637- // to ` scope ` and will attach any allocated resource , such as signal
638- // subscriptions, to the ` scope ` .
763+ // to ` scope ` and will attach any allocated resources , such as signal
764+ // subscriptions.
639765scopedFuntion()
640766
641767// At a later point it can be disposed.
0 commit comments