respondix
Version:
Tiny lightweight proxy-based and zero-dependency reactivity library
122 lines • 4.73 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import Observable from './observable';
import { mapObject } from './utils';
import { watch } from './job';
export function defineReactive(target, name, getter) {
watch(function () { return target[name] = getter(); });
}
export default function makeReactive(state) {
// @ts-ignore
if (state === null || state === void 0 ? void 0 : state.__isProxy)
return state; // Already reactive
if (Array.isArray(state)) {
// @ts-ignore
return makeArrayReactive(state);
}
else if (state instanceof Function) {
// Don't proxy functions
return state;
}
else if (state instanceof Object) {
return makeObjectReactive(state);
}
else {
// Must be primitive. Return as-is
return state;
}
}
export function unwrap(state) {
if (!state.__isProxy)
return state; // Passed state not reactive. Return original object
// Return shallow clone of object. This breaks reactivity
return __assign({}, state);
}
export function deepUnwrap(state) {
// Recursively unwrap object
return mapObject(unwrap(state), deepUnwrap);
}
function makeObjectReactive(obj) {
// Map to hold observables, which in turn hold subscribed jobs, for each property
var observables = new Map();
// Recursively create proxies for object and nested objects
return new Proxy(mapObject(obj, function (p) { return makeReactive(p); }), {
get: function (target, key, receiver) {
// Hard-coded property to identify reactive state
if (key === '__isProxy')
return true;
// Create observable if not already present, add running job as subscriber
if (observables.has(key))
observables.get(key).depend();
else
observables.set(key, new Observable().depend());
// Return requested value. If method, bind to proxy in order to detect mutations
return typeof target[key] === 'function' ? target[key].bind(receiver) : target[key];
},
set: function (target, key, value) {
var _a;
// Exit if mutation has no effect
if (target[key] === value)
return true;
// Assign reactive version of value
target[key] = makeReactive(value);
// Notify subscribers, if any
(_a = observables.get(key)) === null || _a === void 0 ? void 0 : _a.notify();
return true;
},
deleteProperty: function (target, key) {
var _a;
if (!(key in target))
return false;
// Delete value. Notify subscribers
delete target[key];
(_a = observables.get(key)) === null || _a === void 0 ? void 0 : _a.notify();
return true;
}
});
}
function makeArrayReactive(arr) {
// Array only needs one observable since indices are not bound to specific items
var observable = new Observable();
// Create proxy for clone of array with reactive items
return new Proxy(arr.map(function (i) { return makeReactive(i); }), {
get: function (target, key, receiver) {
// Hard-coded property to identify reactive state
if (key === '__isProxy')
return true;
// Add running job to subscribers
observable.depend();
// Return requested value. If method, bind to proxy in order to detect mutations.
// Even works for built-ins like push and pop. Scripting-language magic
return typeof target[key] === 'function' ? target[key].bind(receiver) : target[key];
},
set: function (target, key, value) {
// Exit if mutation has no effect
if (target[key] === value)
return true;
// Assign reactive version of value
target[key] = makeReactive(value);
// Notify subscribers
observable.notify();
return true;
},
deleteProperty: function (target, key) {
if (!(key in target))
return false;
// Delete value. Notify subscribers
delete target[key];
observable.notify();
return true;
}
});
}
//# sourceMappingURL=state.js.map