Skip to content

Instantly share code, notes, and snippets.

@monkeymonk
Created March 16, 2022 13:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monkeymonk/383545c388324edf5c6d19f1d9b450c7 to your computer and use it in GitHub Desktop.
Save monkeymonk/383545c388324edf5c6d19f1d9b450c7 to your computer and use it in GitHub Desktop.
JavaScript reactivity function helper
import debounce from 'lodash.debounce';
/**
* @example
* import { reactive, watch } from './reactivity';
*
* const r1 = reactive({ isReady: false })
* const r2 = reactive({ x: 1 })
*
* setTimeout(() => {
* r1.isReady = true
* }, 1000)
*
* setInterval(() => {
* r2.x++
* }, 500)
*
* watch(() => {
* if (!r1.isReady) return
* console.log(`r2.x: ${r2.x}`)
* })
*/
/**
* @var {Symbol}
*/
const dependencies = new Set();
/**
* @var {{callback: () => *, dependencies: Set}[]}
*/
const watchers = [];
/**
* Watch a reactive property or a computed function on the component instance for changes.
* The callback gets called with the new value and the old value for the given property.
* @param {function} callback
*/
export function watch(callback) {
const watcher = {
callback: debounce(() => {
dependencies.clear();
callback();
watchers.dependencies = new Set(dependencies);
}, 0),
dependencies: new Set(),
};
watcher.callback();
watchers.push(watcher);
}
/**
* Returns a reactive copy of the object.
* @param {*} value
*/
export function reactive(value) {
const keyToSymbolMap = new Map();
const getSymbolForKey = (key) => {
const symbol = keyToSymbolMap.get(key) || Symbol();
if (!keyToSymbolMap.has(key)) {
keyToSymbolMap.set(key, symbol);
}
return symbol;
};
return new Proxy(value, {
get(target, key) {
dependencies.add(getSymbolForKey(key));
return target[key];
},
set(target, key, value) {
target[key] = value;
watchers
.filter(({ dependencies }) => dependencies.has(getSymbolForKey(key)))
.forEach(({ callback }) => {
callback();
});
return true;
},
});
}
/**
* Takes an inner value and returns a reactive and mutable ref object.
* The ref object has a single property .value that points to the inner value.
* @param {*} value
*/
export function ref(value) {
return reactive({ value });
}
/**
* Takes a getter function and returns an immutable reactive ref object for the
* returned value from the getter.
* @param {function} fn
*/
export function computed(fn) {
const r = ref(undefined);
watch(() => {
r.value = fn();
});
return r;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment