1.0.4 • Published 4 years ago

@fluentkit/observable v1.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

@fluentkit/observable

NPM Downloads Bundlephobia Issues License NPM Downloads jsDelivr hits (npm) Unpkg

A lightweight 3KB (minified), 1KB gzipped, zero dependency* object proxy for reactivity with dependency tracking, watchers, effects and cached object getters.

Inspired by VueJs Reactivity.

Watchers and effects are batched, de-duped and called asynchronously using promises for performance.

* Requires the javascript Promise function, and the Reflect api. Internet Explorer is NOT supported.

Usage

To create a reactive object, import the observable function and provide your initial object as its only argument.

import {observable} from '@fluentkit/observable';

const reactiveObj = observable({
    foo: 'bar',
    bazzer: {
        one: 'two',
        three: ['four', 'five', 'six']
    },
    get computedValue() {
        return this.bazzer.one + ',' + this.bazzer.three.join(',');
    }
});

CDN Usage

You can use a prebuilt copy of the package from the sources below:

https://unpkg.com/@fluentkit/observable

https://cdn.jsdelivr.net/npm/@fluentkit/observable

In both case the observable function will be available on the global variable FluentKit:

const obj = FluentKit.observable({});

API

$watch: (PropertyKey | PropertyKey[] | Function, callback?: Function): void

To watch for data changes you can use the watch function:

// when you provide just one argument the watcher is called on all changes:
reactiveObj.$watch((propertyName) => {
    // here propertyName equals the (possibly nested) object key that was changed.
});

// or indicate string property to watch:
reactiveObj.$watch('foo', () => {
    // reactiveObj.foo changed.
});

// and finally watch multiple properties with one callback:
reactiveObj.$watch(['foo', 'bazzer.one'], (propertyName) => {
    // propertyName changed.
});

$watchSync: (PropertyKey | PropertyKey[] | Function, callback?: Function): void

Provides the same api as $watch but runs the callback function immediately. This method is used internally to clear cached computed values, it is exposed but most of your needs should be covered by $watch.

$effect: (callback: () => {}): void

Effect when called first evaluates the supplied callback, tracking any dependencies accessed. Then when those dependencies change the callback is re-run, re-tracking the dependencies.

reactiveObject.$effect(() => {
    console.log('effect called!', 'foo is:', reactiveObj.foo, 'bazzer is:', reactiveObj.bazzer);
});

// >> effect called .....

reactiveObject.bazzer.one = 'three'
// >> effect called .....

reactiveObj.newProperty = 'foobarbazzer'

// >> effect NOT called

$track: (callback: () => {}): string[]

Mainly used internally the $track method returns the property keys accessed during the evaluation of its supplied callback.

const dependencies = reactivObj.$track(() => {
    const foo = reactiveObj.foo;
    const bazzer = reactiveObj.bazzer;
});

// dependencies = ['foo', 'bazzer'];

$nextTick: (callback?: () => {}): Promise

Allows you to run actions after any watchers and effects have been applied for the current observables modifications.

$nextTick returns a promise, so you can provide a then callback, or await $nextTick in async functions.

reactiveObj.foo = 'zab';
// modifications here runs before any watchers/effects on `foo`
await reactiveObj.$nextTick();
// modifications here run AFTER watchers and effects for `foo`

$isSettled: boolean

Indicates if all watchers and effects have been run, and no new items have been passed to the queue.

$isObservable: boolean

Indicates an object is already an observable.

Computed values

Borrowed from Vue, "computed" values are just native object getters which can be defined upon creation:

const obj = observable({
    foo: 'bar',
    get computed() {
        return this.foo.split('').reverse().join('');
    }
});

Or added later using Object.defineProperty:

Object.defineProperty(obj, 'computed', {
    get () {
        return this.foo.split('').reverse().join('');
    }
});

Getters or "computed" properties are great for intensive operations. What's more when accessed their values are cached and returned without re-invoking until one of their dependencies change:

const obj = observable({
    foo: 'bar',
    bazzer: 'rezzab',
    get computed() {
        console.log('called');
        return this.foo.split('').reverse().join('');
    }
});

let computed = obj.computed; // === rab
// >> called
computed = obj.computed; // === rab
obj.bazzer = 'bazzer';
computed = obj.computed; // === rab
computed = obj.computed; // === rab
computed = obj.computed; // === rab
// >> NOT called
obj.foo = 'rab';
computed = obj.computed; // === bar
// >> called
computed = obj.computed; // === bar
computed = obj.computed; // === bar
computed = obj.computed; // === bar
// >> NOT called

Nested Observables

Observables can be nested and watched, to watch the whole child object:

obj.child = observable({ foo: 'bar' });

obj.$watch('child', () => {
    // child, was reassigned, deleted, or its internal values changed.
});

obj.$watch('child.foo', () => {
    // child.foo, was reassigned, deleted, or its value changed.
});
1.0.4

4 years ago

1.0.3

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago