From 05200ebee375654f81af7dda3de4593b797ff1c2 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Wed, 18 Jan 2017 08:51:50 -0600 Subject: [PATCH] Add documentation for frptools. --- README.md | 11 ++- packages/frptools/README.md | 79 ++++++++++++++++++++ packages/frptools/src/computed.js | 51 +++++++++++++ packages/frptools/src/index.js | 111 +--------------------------- packages/frptools/src/observable.js | 20 +++++ 5 files changed, 162 insertions(+), 110 deletions(-) create mode 100644 packages/frptools/README.md create mode 100644 packages/frptools/src/computed.js create mode 100644 packages/frptools/src/observable.js diff --git a/README.md b/README.md index f606d5e..9d4b7fe 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -repo +# Portfolio + +These are my personal projects. + +# Packages + +## [FRP tools](./packages/frptools/README.md) + +Observable and computed value stores designed to work together for storing real state and derived +state. diff --git a/packages/frptools/README.md b/packages/frptools/README.md new file mode 100644 index 0000000..5e2b5fd --- /dev/null +++ b/packages/frptools/README.md @@ -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. diff --git a/packages/frptools/src/computed.js b/packages/frptools/src/computed.js new file mode 100644 index 0000000..9834ac0 --- /dev/null +++ b/packages/frptools/src/computed.js @@ -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(); diff --git a/packages/frptools/src/index.js b/packages/frptools/src/index.js index 38666a8..240f46c 100644 --- a/packages/frptools/src/index.js +++ b/packages/frptools/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'; diff --git a/packages/frptools/src/observable.js b/packages/frptools/src/observable.js new file mode 100644 index 0000000..6d159db --- /dev/null +++ b/packages/frptools/src/observable.js @@ -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; +}