Add documentation for frptools.
This commit is contained in:
parent
4e70d07f71
commit
d5c1cd852b
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# FRP tools
|
||||
|
||||
Observer and computed value stores designed to work together for storing real state and derived
|
||||
state.
|
||||
|
||||
# [observable](./src/observable.js)
|
||||
|
||||
`observable` is a simple value store that can report when its value changes. It is good for wrapping
|
||||
external props passed into a component so compute types can dependent on them.
|
||||
|
||||
## Usage
|
||||
|
||||
### Creation
|
||||
|
||||
Creates and sets initial value to `true`
|
||||
|
||||
```js
|
||||
const inViewport = observable(true);
|
||||
```
|
||||
|
||||
### Read
|
||||
|
||||
Call it to receive the stored value.
|
||||
|
||||
```js
|
||||
if (inViewport()) {
|
||||
/* inViewport is truthy */
|
||||
}
|
||||
```
|
||||
|
||||
### Change
|
||||
|
||||
Call it passing the new value. If any computed stores depend on this value they will be marked dirty
|
||||
and re-evaluated the next time they are read from.
|
||||
|
||||
```js
|
||||
inViewport(false);
|
||||
```
|
||||
|
||||
### Subscribe to changes
|
||||
|
||||
Call the subscribe method with a callback that will be called when the observable is changed to a
|
||||
different value.
|
||||
|
||||
```js
|
||||
inViewport.subscribe(console.log.bind(console));
|
||||
```
|
||||
|
||||
# [computed](./src/computed.js)
|
||||
|
||||
`computed` is a functional store that depends on the values of observables or other computeds. They
|
||||
derive value from observables rather than store value and hence cannot be set directly.
|
||||
|
||||
## Behavior
|
||||
|
||||
A `computed` will subscribe to its dependencies in such a way that it will be marked as _dirty_ when
|
||||
any dependency changes. Whenever it is read from, if will recompute if it is dirty, otherwise it
|
||||
just return the stored result from the last time it computed.
|
||||
|
||||
## Usage
|
||||
|
||||
### Creation
|
||||
|
||||
```js
|
||||
const showDialog = computed(
|
||||
(inVP, shouldShow) => inVP && shouldShow, // computation function
|
||||
[inViewport, shouldShow] // array of dependencies, can be either observable or computed
|
||||
);
|
||||
```
|
||||
|
||||
### Read
|
||||
|
||||
```js
|
||||
if (showDialog()) {
|
||||
/* showDialog() is truthy */
|
||||
}
|
||||
```
|
||||
|
||||
Call it to receive the stored value, recomputing if necessary.
|
||||
51
src/computed.js
Normal file
51
src/computed.js
Normal file
@ -0,0 +1,51 @@
|
||||
export function computed(fn, dependencies = []) {
|
||||
const subscribers = new Set();
|
||||
const dependents = new Set();
|
||||
let val = undefined;
|
||||
let isDirty = true;
|
||||
|
||||
function _computedDirtyReporter() {
|
||||
if (!isDirty) {
|
||||
isDirty = true;
|
||||
}
|
||||
dependents.forEach(runParam);
|
||||
|
||||
if (subscribers.size) {
|
||||
accessor();
|
||||
}
|
||||
}
|
||||
|
||||
const dependentSubscriptions = Array.from(dependencies).map(d => d._d(_computedDirtyReporter));
|
||||
|
||||
const accessor = function _computed() {
|
||||
if (isDirty) {
|
||||
const newVal = fn.apply(null, dependencies.map(runParam));
|
||||
isDirty = false;
|
||||
if (newVal !== val) {
|
||||
val = newVal;
|
||||
subscribers.forEach(s => s(val));
|
||||
}
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
accessor.subscribe = fn => {
|
||||
subscribers.add(fn);
|
||||
return () => subscribers.delete(fn);
|
||||
};
|
||||
|
||||
accessor._d = fn => {
|
||||
dependents.add(fn);
|
||||
return () => dependents.delete(fn);
|
||||
};
|
||||
|
||||
accessor.detach = () => {
|
||||
subscribers.clear();
|
||||
dependents.clear();
|
||||
dependentSubscriptions.forEach(runParam);
|
||||
};
|
||||
|
||||
return accessor;
|
||||
}
|
||||
|
||||
const runParam = a => a();
|
||||
111
src/index.js
111
src/index.js
@ -1,109 +1,2 @@
|
||||
// observable is a simple value store that can report when its value changes.
|
||||
// It is good for wrapping external props passed into a component so compute
|
||||
// types can dependent on them.
|
||||
// Usage:
|
||||
//
|
||||
// Creation:
|
||||
// `const inViewport = observable(true);`
|
||||
// Creates and sets initial value to `true`
|
||||
//
|
||||
// Read:
|
||||
// `if (inViewport()) { }`
|
||||
// Call it to receive the stored value.
|
||||
//
|
||||
// Change:
|
||||
// `inViewport(false);`
|
||||
// Call it passing the new value. If any computed stores depend on this value
|
||||
// they will be marked dirty and re-evaluated the next time they are read from.
|
||||
//
|
||||
// Subscribe to changes:
|
||||
// `inViewport.subscribe(console.log.bind(console))`
|
||||
// Call the subscribe method with a callback that will be called when the
|
||||
// observable is changed to a different value.
|
||||
|
||||
export function observable(store) {
|
||||
const subscribers = new Set();
|
||||
|
||||
const accessor = function _observable(newVal) {
|
||||
if (newVal !== undefined && store !== newVal) {
|
||||
store = newVal;
|
||||
subscribers.forEach(s => s(store));
|
||||
}
|
||||
return store;
|
||||
};
|
||||
|
||||
accessor.subscribe = accessor._d = fn => {
|
||||
subscribers.add(fn);
|
||||
return () => subscribers.delete(fn);
|
||||
};
|
||||
|
||||
accessor.unsubscribeAll = () => subscribers.clear();
|
||||
|
||||
return accessor;
|
||||
}
|
||||
|
||||
// computed is a functional store that depends on the values of observables or other computeds. They cannot be set directly.
|
||||
//
|
||||
// Behavior:
|
||||
// computed will subscribe to its dependencies in such a way that it will be marked as "dirty" when any dependency changes.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Creation:
|
||||
// const showDialog = computed((inVP, shouldShow) => (inVP && shouldShow), [inViewport, shouldShow]);
|
||||
//
|
||||
// Read:
|
||||
// `if (showDialog()) { alert("Hi"); }`
|
||||
// Call it to receive the stored value.
|
||||
|
||||
export function computed(fn, dependencies = []) {
|
||||
const subscribers = new Set();
|
||||
const dependents = new Set();
|
||||
let val = undefined;
|
||||
let isDirty = true;
|
||||
|
||||
function _computedDirtyReporter() {
|
||||
if (!isDirty) {
|
||||
isDirty = true;
|
||||
}
|
||||
dependents.forEach(runParam);
|
||||
|
||||
if (subscribers.size) {
|
||||
accessor();
|
||||
}
|
||||
}
|
||||
|
||||
const dependentSubscriptions = Array.from(dependencies).map(d => d._d(_computedDirtyReporter));
|
||||
|
||||
const accessor = function _computed() {
|
||||
if (isDirty) {
|
||||
const newVal = fn.apply(null, dependencies.map(runParam));
|
||||
isDirty = false;
|
||||
if (newVal !== val) {
|
||||
val = newVal;
|
||||
subscribers.forEach(s => s(val));
|
||||
}
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
accessor.subscribe = fn => {
|
||||
subscribers.add(fn);
|
||||
return () => subscribers.delete(fn);
|
||||
};
|
||||
|
||||
accessor._d = fn => {
|
||||
dependents.add(fn);
|
||||
return () => dependents.delete(fn);
|
||||
};
|
||||
|
||||
accessor.detach = () => {
|
||||
subscribers.clear();
|
||||
dependents.clear();
|
||||
dependentSubscriptions.forEach(runParam);
|
||||
};
|
||||
|
||||
return accessor;
|
||||
}
|
||||
|
||||
const runParam = a => a();
|
||||
export { observable } from './observable';
|
||||
export { computed } from './computed';
|
||||
|
||||
20
src/observable.js
Normal file
20
src/observable.js
Normal file
@ -0,0 +1,20 @@
|
||||
export function observable(store) {
|
||||
const subscribers = new Set();
|
||||
|
||||
const accessor = function _observable(newVal) {
|
||||
if (newVal !== undefined && store !== newVal) {
|
||||
store = newVal;
|
||||
subscribers.forEach(s => s(store));
|
||||
}
|
||||
return store;
|
||||
};
|
||||
|
||||
accessor.subscribe = accessor._d = fn => {
|
||||
subscribers.add(fn);
|
||||
return () => subscribers.delete(fn);
|
||||
};
|
||||
|
||||
accessor.unsubscribeAll = () => subscribers.clear();
|
||||
|
||||
return accessor;
|
||||
}
|
||||
Reference in New Issue
Block a user