Observable β
Create observable state β
You can create an observable state in two ways: by extending the Observable
class, or by applying the makeObservable
function to an existing object.
import { makeObservable } from 'kr-observable'
const state = makeObservable({
foo: 'bar'
})
import { Observable } from 'kr-observable'
class State extends Observable {
foo = 'bar';
}
As a result, you get the following features out of the box:
- All properties are made observable, including those added dynamically;
- Values of type
Array
,Map
,Set
and plain objects become deeply observable; - Getters are treated as computed properties, meaning their values are cached and only re-evaluated when dependencies change;
- All methods are automatically bound to target, so you donβt need to manually bind
this
;
In addition, when using class syntax:
- Private fields and methods (e.g., using
#privateField
syntax) are allowed β they remain private and are not made reactive;Example
jsclass State extends Observable { // allowed #privateFiled = ''; // allowed #privateMethod() { } // allowed get #privateGetter() { } }
- Subclassing (inheritance) is allowed β you can safely extend reactive classes, and reactivity will work correctly across the inheritance chain.
Example
jsclass State extends Observable { a = 1; } class SecondState extends State { b = 2 } class ThirdState extends SecondState { c = 3 } console.log(new ThirdState()) // { a: 1, b: 2, c: 3 }
Observables stay native β
An observable object remains a standard JavaScript object, with its properties left intact β neither redefined, converted into getter
/setter
pairs, nor transformed into constructs like Signals
or Atoms
.
Limitations β
Custom and built-in classes aren't deeply observed
In addition to instances of Map
, Set
, Array
, and plain Object
, instances of other built-in or custom classes will not be automatically made deeply observable.
import { Observable } from 'kr-observable';
class Foo {};
class Bar extends Obsevable {};
class Baz extends Obsevable {
reg = new RegExp(); // non-deeply observable
foo = new Foo(); // non-deeply observable
bar = new Bar(); // deeply observable
}
makeObservable requires plain objects
The makeObservable
function only accepts plain objects or objects without a prototype. Otherwise, it throws an error.
import { makeObservable } from 'kr-observable'
let state
state = makeObservable({}); // ok
state = makeObservable(Object.create(null)); // ok
class Foo {}
state = makeObservable(new Foo()); // error
state = makeObservable([]); // error
Ignoring properties β
You can prevent certain properties from being tracked by adding them to the ignore list. Once added, those properties won't become reactive, and changes to them won't cause any reactions or side effects.
The ignore list is simply a Set
that holds the names of properties you want to exclude from observation.
Syntax β
import { makeObservable } from 'kr-observable'
const ignore = new Set(['id']);
// id will not be observable
makeObservable({ id: 1, name: '' }, ignore);
import { Observable } from 'kr-observable';
class User extends Observable {
static ignore = new Set(['id']);
// id will not be observable
id = 1;
name = '';
}
Limitations β
Static properties are not inherited
If you prefer using class syntax, keep in mind that static properties such as ignore
are not automatically inherited by subclasses. If you want to reuse or extend ignored properties from a parent class, you must explicitly redefine the ignore list in each subclass.
For example, in the code below, all properties of SuperUser
will be observable, while in User
, only name
and role
are observable β any ignored properties from the parent class won't take effect unless redeclared.
import { Observable } from 'kr-observable';
class User extends Observable {
static ignore = new Set(['id']);
id: string; // non-observable
name: string; // observable
role: string; // observable
}
class SuperUser extends User { }
Shallow observation β
You can control which properties should not be made deeply observable by adding them to the shallow observation list. Once added, those properties will still be observed, but changes to their nested values won't be tracked recursively β meaning updates deep inside these properties won't trigger reactions or effects.
The shallow list is simply a Set
that holds the names of properties you want to observe only at the top level.
Syntax β
import { makeObservable } from 'kr-observable'
const shallow = new Set(['arr']);
makeObservable(
{
id: 1,
arr: [] // non-deeply observable
},
undefined, // instead of ignore list
shallow
);
import { Observable } from 'kr-observable';
class User extends Observable {
static shallow = new Set(['arr']);
id = 1;
// non-deeply observable
arr = [];
}
When using class syntax
Similar to the ignore
property, the shallow
property is not automatically inherited. You must explicitly define it in each subclass if needed.
Example β
import { Observable, autorun } from 'kr-observable';
class State extends Observable {
static shallow = new Set(['a','b']);
a = {
foo: 1
};
b = new Array(100000);
}
const state = new State()
state.a = {}; // will trigger reactions/effects
state.b = []; // will trigger reactions/effects
state.b.push(1); // won't trigger reactions/effects
state.a.foo += 1; // won't trigger reactions/effects
In most cases, there is no need to use ignore
or shallow
. These options become useful primarily when working with large or complex objects that you intentionally want to exclude from reactivity β for example, to optimize performance or prevent excessive tracking.