Created
March 16, 2022 13:50
-
-
Save monkeymonk/383545c388324edf5c6d19f1d9b450c7 to your computer and use it in GitHub Desktop.
JavaScript reactivity function helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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