MadroneJS core
Madrone is an easy way to make reactive objects in JS.
Basics
MadroneJS has 3 basic concepts:
reactiveproperties: something that will be tracked when it is retrieved or setcomputedproperties: a getter property that will only re-evaluate when its reactive dependencies changewatch: something that can watch changes that happen to reactive or computed properties
These three ideas aren't particularly novel, and several other libraries implement ways to do this, such as MobX or Vue. While MadroneJS can be used on its own, it was originally written to provide an easy interface to integrate with other frameworks (namely Vue).
Using MadroneJS allows for the service layer of an application to take advantage of computed and reactive properties, but avoids tying the service layer to one particular view layer. This makes it possible to write the core service layer of an application or library once, and reuse that code in a Vue 2 application or a Vue 3 application while maintaining reactivity.
Installation
pnpm add @madronejs/corepnpm add @madronejs/corenpm install @madronejs/core --savenpm install @madronejs/core --saveyarn add @madronejs/coreyarn add @madronejs/coreSetup
To get MadroneJS up and running, it needs to have an integration registered so it knows how to make the objects reactive. It does this through a combination of defineProperty and using js Proxies.
import Madrone, { MadroneState } from '@madronejs/core';
Madrone.use(MadroneState);import Madrone, { MadroneState } from '@madronejs/core';
Madrone.use(MadroneState);import Madrone, { MadroneVue3 } from '@madronejs/core';
import { reactive, computed, watch, toRaw } from 'vue';
Madrone.use(MadroneVue3({
reactive, computed, watch, toRaw,
}));import Madrone, { MadroneVue3 } from '@madronejs/core';
import { reactive, computed, watch, toRaw } from 'vue';
Madrone.use(MadroneVue3({
reactive, computed, watch, toRaw,
}));import Madrone, { MadroneVue2 } from '@madronejs/core';
import Vue from 'vue';
Madrone.use(MadroneVue2(Vue));import Madrone, { MadroneVue2 } from '@madronejs/core';
import Vue from 'vue';
Madrone.use(MadroneVue2(Vue));Usage
MadroneJS can be used with regular objects or it can be used with classes using decorators.
Objects
You can use auto to quickly mark all properties as reactive, and all getters as computed/cached properties. watch can be used to observe changes.
import { auto, watch } from '@madronejs/core';
const person = auto({
name: 'Jim',
get greeting() {
return `Hi, I'm ${person.name}`
},
});
const newVals = [];
const oldVals = [];
// Watch a reactive property when it changes. Any reactive property accessed
// in the first argument will cause the watcher callback to trigger. Anything
// returned from the first argument will define what the newVal/oldVal is.
watch(() => person.greeting, (newVal, oldVal) => {
newVals.push(newVal);
oldVals.push(oldVal);
});
person.name; // Jim
person.greeting; // Hi, I'm Jim
person.name = 'Not Jim';
person.greeting; // Hi, I'm Not Jim
// watcher is async...
console.log('New Vals:', newVals); // ["Hi, I'm Not Jim"]
console.log('Old Vals:', oldVals); // ["Hi, I'm Jim"]import { auto, watch } from '@madronejs/core';
const person = auto({
name: 'Jim',
get greeting() {
return `Hi, I'm ${person.name}`
},
});
const newVals = [];
const oldVals = [];
// Watch a reactive property when it changes. Any reactive property accessed
// in the first argument will cause the watcher callback to trigger. Anything
// returned from the first argument will define what the newVal/oldVal is.
watch(() => person.greeting, (newVal, oldVal) => {
newVals.push(newVal);
oldVals.push(oldVal);
});
person.name; // Jim
person.greeting; // Hi, I'm Jim
person.name = 'Not Jim';
person.greeting; // Hi, I'm Not Jim
// watcher is async...
console.log('New Vals:', newVals); // ["Hi, I'm Not Jim"]
console.log('Old Vals:', oldVals); // ["Hi, I'm Jim"]Class decorators
Using decorators can make code more readable, and can give more control over which properties are marked as reactive and which getters are considered computed getters.
import { computed, reactive } from '@madronejs/core'
class Person {
@reactive name: string;
@computed get greeting() {
return `Hi, I'm ${this.name}`;
}
constructor(options: { name: string }) {
this.name = options?.name;
}
}import { computed, reactive } from '@madronejs/core'
class Person {
@reactive name: string;
@computed get greeting() {
return `Hi, I'm ${this.name}`;
}
constructor(options: { name: string }) {
this.name = options?.name;
}
}