@goldfishjs/reactive
Advanced tools
Comparing version 0.0.18 to 1.0.0
@@ -23,4 +23,6 @@ const libraryName = require('./package.json').name; | ||
generateKeyPathString: `${codeDir}/generateKeyPathString`, | ||
call: `${codeDir}/call`, | ||
getCurrent: `${codeDir}/getCurrent`, | ||
}[name]; | ||
}, | ||
}; |
@@ -72,3 +72,3 @@ import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; | ||
if (!shouldBatch) { | ||
if (!shouldBatch || options.isArray) { | ||
cb(n, o, options); | ||
@@ -80,7 +80,3 @@ } else { | ||
removeListeners.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
isDone = true; | ||
removeListeners.splice(0, removeListeners.length); | ||
Promise.resolve().then(function () { | ||
@@ -95,3 +91,8 @@ return cb(n, o, options); | ||
}); | ||
return removeListeners; | ||
return [function () { | ||
removeListeners.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
removeListeners.splice(0, removeListeners.length); | ||
}]; | ||
} | ||
@@ -106,2 +107,9 @@ }]); | ||
} | ||
/** | ||
* Collect dependencies in `fn`. | ||
* | ||
* @param fn | ||
* @param errorCb | ||
*/ | ||
export function call(fn, errorCb) { | ||
@@ -113,3 +121,9 @@ stack.push(new DepList()); | ||
} catch (error) { | ||
errorCb && errorCb(error); | ||
if (errorCb) { | ||
errorCb(error); | ||
} else { | ||
throw error; | ||
} | ||
; | ||
} finally { | ||
@@ -116,0 +130,0 @@ stack.pop(); |
@@ -21,2 +21,3 @@ export { default as observable } from './observable'; | ||
export { default as unraw } from './unraw'; | ||
export { default as markObservable } from './markObservable'; | ||
export { default as markObservable } from './markObservable'; | ||
export { default as call } from './call'; |
/* eslint-disable @typescript-eslint/no-use-before-define */ | ||
import { getCurrent, Dep } from './dep'; | ||
import { isArray } from './utils'; | ||
import { isArray, genId } from './utils'; | ||
import silentValue, { isSilentValue } from './silentValue'; | ||
@@ -9,2 +9,3 @@ import { isRaw } from './raw'; | ||
var OBSERVE_KEY = '__reactive-ob__'; | ||
var NOTIFY_KEY = '__notify'; | ||
export function isObservable(obj) { | ||
@@ -35,3 +36,3 @@ return obj && Object.prototype.hasOwnProperty.call(obj, OBSERVE_KEY) && obj[OBSERVE_KEY] === OBSERVE_FLAG; | ||
var result = oldMethod.call.apply(oldMethod, [this].concat(args)); | ||
var result = oldMethod.apply(this, args); | ||
@@ -45,12 +46,9 @@ if (isObservable(this)) { | ||
var notify = this.__notify; | ||
if (notify) { | ||
notify(this, this, { | ||
args: args, | ||
oldV: oldV, | ||
isArray: true, | ||
method: methodName | ||
}); | ||
} | ||
callNotifyFns(this, this, this, { | ||
args: args, | ||
result: result, | ||
oldV: oldV, | ||
isArray: true, | ||
method: methodName | ||
}); | ||
} | ||
@@ -62,2 +60,53 @@ | ||
function defineNotify(value, dep, notifyId) { | ||
if (isObject(value)) { | ||
if (!value[NOTIFY_KEY]) { | ||
Object.defineProperty(value, NOTIFY_KEY, { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: {} | ||
}); | ||
} | ||
var notifyFns = value[NOTIFY_KEY]; | ||
var notifyFn = function notifyFn(n, o, options) { | ||
dep.notifyChange(n, o, Object.assign({ | ||
type: 'notify' | ||
}, options || {})); | ||
}; | ||
notifyFns[notifyId] = notifyFn; | ||
} | ||
} | ||
function removeNotify(value, notifyId) { | ||
if (isObject(value)) { | ||
var notifyFns = value[NOTIFY_KEY]; | ||
if (notifyFns) { | ||
notifyFns[notifyId] = null; | ||
} | ||
} | ||
} | ||
function callNotifyFns(value) { | ||
if (isObject(value)) { | ||
var notifyFns = value[NOTIFY_KEY]; | ||
if (notifyFns) { | ||
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
for (var key in notifyFns) { | ||
if (notifyFns[key]) { | ||
notifyFns[key].apply(notifyFns, args); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function defineProperty(obj, key) { | ||
@@ -72,2 +121,4 @@ var desc = Object.getOwnPropertyDescriptor(obj, key); // Do not modify the un-configurable properties and `getter/setter` properties. | ||
var dep = new Dep(obj, key); | ||
var notifyId = genId("notify-".concat(key, "-")); | ||
defineNotify(value, dep, notifyId); | ||
Object.defineProperty(obj, key, { | ||
@@ -81,15 +132,2 @@ configurable: typeof key === 'number', | ||
currentDepList.add(dep); | ||
if (isObject(value)) { | ||
Object.defineProperty(value, '__notify', { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: function value(n, o, options) { | ||
dep.notifyChange(n, o, Object.assign({ | ||
type: 'notify' | ||
}, options || {})); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -108,2 +146,8 @@ | ||
if (oldValue !== value && isObject(oldValue)) { | ||
removeNotify(oldValue, notifyId); | ||
} | ||
defineNotify(value, dep, notifyId); | ||
if (!isSilentValue(v)) { | ||
@@ -152,7 +196,3 @@ dep.notifyChange(newValue, oldValue); | ||
obj[name] = silent ? silentValue(value) : value; | ||
var notify = obj.__notify; | ||
if (notify) { | ||
notify(obj, obj); | ||
} | ||
callNotifyFns(obj, obj, obj); | ||
} else { | ||
@@ -159,0 +199,0 @@ obj[name] = silent ? silentValue(value) : value; |
@@ -11,7 +11,7 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; | ||
var watchKeys = function watchKeys(data) { | ||
var stopList = watchDeep(data, setData, { | ||
var stop = watchDeep(data, setData, { | ||
onError: onError, | ||
immediate: true | ||
}); | ||
return stopList; | ||
return [stop]; | ||
}; | ||
@@ -18,0 +18,0 @@ |
@@ -6,4 +6,5 @@ export function isArray(v) { | ||
export function genId() { | ||
var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'g-'; | ||
id += 1; | ||
return id; | ||
return "".concat(prefix).concat(id); | ||
} |
@@ -0,1 +1,2 @@ | ||
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; | ||
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; | ||
@@ -6,2 +7,3 @@ import _createClass from "@babel/runtime/helpers/createClass"; | ||
import { isObject } from '@goldfishjs/utils'; | ||
import isRaw from './isRaw'; | ||
@@ -16,3 +18,3 @@ var Watcher = | ||
this.isFirstTime = true; | ||
this.removeListenersGroup = []; | ||
this.removeListeners = []; | ||
this.fn = fn; | ||
@@ -28,8 +30,11 @@ this.cb = cb; | ||
this.isStopped = true; | ||
this.removeListenersGroup.forEach(function (group) { | ||
group.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
this.callRemoveListeners(); | ||
} | ||
}, { | ||
key: "callRemoveListeners", | ||
value: function callRemoveListeners() { | ||
this.removeListeners.forEach(function (r) { | ||
return r(); | ||
}); | ||
this.removeListenersGroup = []; | ||
this.removeListeners.splice(0, this.removeListeners.length); | ||
} // 递归访问一下,方便搜集依赖 | ||
@@ -40,2 +45,6 @@ | ||
value: function deepVisit(obj) { | ||
if (obj && isRaw(obj)) { | ||
return; | ||
} | ||
if (isObject(obj)) { | ||
@@ -62,2 +71,4 @@ for (var key in obj) { | ||
call(function () { | ||
var _this$removeListeners; | ||
result = _this.fn(); | ||
@@ -69,3 +80,5 @@ | ||
_this.removeListenersGroup.push(getCurrent().addChangeListener(function (n, o, options) { | ||
(_this$removeListeners = _this.removeListeners).push.apply(_this$removeListeners, _toConsumableArray(getCurrent().addChangeListener(function (n, o, options) { | ||
_this.callRemoveListeners(); | ||
if (_this.isStopped) { | ||
@@ -84,3 +97,3 @@ return; | ||
} | ||
})); | ||
}))); | ||
}, this.options.onError); | ||
@@ -87,0 +100,0 @@ |
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; | ||
import _typeof from "@babel/runtime/helpers/typeof"; | ||
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; | ||
import _createClass from "@babel/runtime/helpers/createClass"; | ||
import watch from './watch'; | ||
import { getCurrent } from './dep'; | ||
import { isObject } from '@goldfishjs/utils'; | ||
import isRaw from './isRaw'; | ||
import generateKeyPathString from './generateKeyPathString'; | ||
import { call } from './dep'; | ||
var StopFns = | ||
/*#__PURE__*/ | ||
function () { | ||
function StopFns() { | ||
_classCallCheck(this, StopFns); | ||
this.fns = {}; | ||
} | ||
_createClass(StopFns, [{ | ||
key: "add", | ||
value: function add(keyPathList, curKey, fn) { | ||
var keyPathString = generateKeyPathString(keyPathList); | ||
if (!this.fns[keyPathString]) { | ||
this.fns[keyPathString] = {}; | ||
} | ||
if (this.fns[keyPathString][curKey]) { | ||
throw new Error("Duplicate stop function for key: ".concat(generateKeyPathString([].concat(_toConsumableArray(keyPathList), [curKey])))); | ||
} | ||
this.fns[keyPathString][curKey] = fn; | ||
} | ||
}, { | ||
key: "remove", | ||
value: function remove(keyPathList) { | ||
var keyPathString = generateKeyPathString(keyPathList); | ||
for (var kps in this.fns) { | ||
if (kps.indexOf(keyPathString) === 0) { | ||
for (var k in this.fns[kps]) { | ||
this.fns[kps][k](); | ||
} | ||
this.fns[kps] = {}; | ||
} | ||
} | ||
} | ||
}, { | ||
key: "callAll", | ||
value: function callAll() { | ||
for (var k1 in this.fns) { | ||
for (var k2 in this.fns[k1]) { | ||
this.fns[k1][k2](); | ||
} | ||
} | ||
} | ||
}, { | ||
key: "removeAll", | ||
value: function removeAll() { | ||
this.callAll(); | ||
this.fns = {}; | ||
} | ||
}]); | ||
return StopFns; | ||
}(); | ||
var Watcher = | ||
@@ -14,2 +75,3 @@ /*#__PURE__*/ | ||
this.stopFns = new StopFns(); | ||
this.obj = obj; | ||
@@ -21,48 +83,51 @@ this.callback = callback; | ||
_createClass(Watcher, [{ | ||
key: "iterate", | ||
value: function iterate(curObj) { | ||
key: "watchObj", | ||
value: function watchObj(obj, keyPathList) { | ||
var _this = this; | ||
var keyPathList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
var baseWatch = this.options && this.options.customWatch || watch; | ||
var stopList = []; | ||
if (isRaw(obj)) { | ||
return; | ||
} | ||
if (curObj && _typeof(curObj) === 'object') { | ||
var iterate = function iterate(key) { | ||
var keyStopList = []; | ||
stopList.push(baseWatch(function () { | ||
return curObj[key]; | ||
}, function (newV, oldV, options) { | ||
_this.callback && _this.callback(_this.obj, [].concat(_toConsumableArray(keyPathList), [key]), newV, oldV, options); // If the old value is an object, then clear all children watchers on it. | ||
this.stopFns.remove(keyPathList); | ||
if (isObject(oldV)) { | ||
keyStopList.forEach(function (stop) { | ||
return stop(); | ||
}); | ||
keyStopList.splice(0, keyStopList.length); | ||
} // If the new value is an object, then set watchers on it's children. | ||
if (Array.isArray(obj)) { | ||
obj.forEach(function (_, index) { | ||
_this.watchSingleKey(obj, index, keyPathList); | ||
}); | ||
} else if (isObject(obj)) { | ||
for (var key in obj) { | ||
this.watchSingleKey(obj, key, keyPathList); | ||
} | ||
} | ||
} | ||
}, { | ||
key: "watchSingleKey", | ||
value: function watchSingleKey(obj, key, keyPathList) { | ||
var _this2 = this; | ||
var nextKeyPathList = [].concat(_toConsumableArray(keyPathList), [key]); | ||
call(function () { | ||
obj[key]; | ||
var stopList = getCurrent().addChangeListener(function (newV, oldV, options) { | ||
_this2.watchObj(newV, nextKeyPathList); | ||
if (isObject(newV)) { | ||
keyStopList.push.apply(keyStopList, _toConsumableArray(_this.iterate(newV, [].concat(_toConsumableArray(keyPathList), [key])))); | ||
} | ||
}, _this.options)); | ||
_this2.callback(_this2.obj, nextKeyPathList, newV, oldV, options); | ||
}, false); | ||
if (!_this.options || !_this.options.immediate) { | ||
keyStopList.push.apply(keyStopList, _toConsumableArray(_this.iterate(curObj[key], [].concat(_toConsumableArray(keyPathList), [key])))); | ||
} | ||
}; | ||
_this2.stopFns.add(keyPathList, key, function () { | ||
return stopList.forEach(function (s) { | ||
return s(); | ||
}); | ||
}); | ||
}, function (e) { | ||
var _a; | ||
if (Array.isArray(curObj)) { | ||
curObj.forEach(function (_, index) { | ||
return iterate(index); | ||
}); | ||
if ((_a = _this2.options) === null || _a === void 0 ? void 0 : _a.onError) { | ||
_this2.options.onError(e); | ||
} else { | ||
for (var key in curObj) { | ||
iterate(key); | ||
} | ||
throw e; | ||
} | ||
} | ||
return stopList; | ||
}); | ||
this.watchObj(obj[key], nextKeyPathList); | ||
} | ||
@@ -72,4 +137,15 @@ }, { | ||
value: function watch() { | ||
return this.iterate(this.obj); | ||
var _a; | ||
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.immediate) { | ||
this.callback(this.obj, [], this.obj, undefined); | ||
} | ||
return this.watchObj(this.obj, []); | ||
} | ||
}, { | ||
key: "stop", | ||
value: function stop() { | ||
this.stopFns.removeAll(); | ||
} | ||
}]); | ||
@@ -82,3 +158,6 @@ | ||
var watcher = new Watcher(obj, callback, options); | ||
return watcher.watch(); | ||
watcher.watch(); | ||
return function () { | ||
return watcher.stop(); | ||
}; | ||
} |
{ | ||
"name": "@goldfishjs/reactive", | ||
"version": "0.0.18", | ||
"version": "1.0.0", | ||
"description": "Reactive.", | ||
@@ -18,3 +18,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@goldfishjs/utils": "^0.0.18" | ||
"@goldfishjs/utils": "^1.0.0" | ||
}, | ||
@@ -36,3 +36,2 @@ "devDependencies": { | ||
"merge2": "^1.3.0", | ||
"pre-commit": "^1.2.2", | ||
"react": "^16.6.1", | ||
@@ -39,0 +38,0 @@ "react-dom": "^16.6.1", |
@@ -82,3 +82,3 @@ import { Methods } from './observable'; | ||
if (!shouldBatch) { | ||
if (!shouldBatch || options.isArray) { | ||
cb(n, o, options); | ||
@@ -90,5 +90,3 @@ } else { | ||
removeListeners.forEach(fn => fn()); | ||
isDone = true; | ||
removeListeners.splice(0, removeListeners.length); | ||
Promise.resolve().then(() => cb(n, o, options)); | ||
@@ -100,3 +98,8 @@ } | ||
}); | ||
return removeListeners; | ||
return [ | ||
() => { | ||
removeListeners.forEach(fn => fn()); | ||
removeListeners.splice(0, removeListeners.length); | ||
}, | ||
]; | ||
} | ||
@@ -115,2 +118,8 @@ } | ||
/** | ||
* Collect dependencies in `fn`. | ||
* | ||
* @param fn | ||
* @param errorCb | ||
*/ | ||
export function call(fn: Function, errorCb?: IErrorCallback) { | ||
@@ -121,3 +130,7 @@ stack.push(new DepList()); | ||
} catch (error) { | ||
errorCb && errorCb(error); | ||
if (errorCb) { | ||
errorCb(error); | ||
} else { | ||
throw error; | ||
}; | ||
} finally { | ||
@@ -124,0 +137,0 @@ stack.pop(); |
@@ -35,1 +35,3 @@ export { default as observable } from './observable'; | ||
export { default as markObservable } from './markObservable'; | ||
export { default as call } from './call'; |
/* eslint-disable @typescript-eslint/no-use-before-define */ | ||
import { getCurrent, Dep, ChangeOptions } from './dep'; | ||
import { isArray } from './utils'; | ||
import { isArray, genId } from './utils'; | ||
import silentValue, { isSilentValue } from './silentValue'; | ||
@@ -18,2 +18,4 @@ import { isRaw } from './raw'; | ||
const NOTIFY_KEY = '__notify'; | ||
export function isObservable(obj: any) { | ||
@@ -44,3 +46,3 @@ return obj | ||
const oldV = this.slice(0); | ||
const result = oldMethod.call(this, ...args); | ||
const result = oldMethod.apply(this, args); | ||
@@ -54,11 +56,9 @@ if (isObservable(this)) { | ||
const notify = (this as any).__notify; | ||
if (notify) { | ||
notify(this, this, { | ||
args, | ||
oldV, | ||
isArray: true, | ||
method: methodName, | ||
}); | ||
} | ||
callNotifyFns(this, this, this, { | ||
args, | ||
result, | ||
oldV, | ||
isArray: true, | ||
method: methodName, | ||
}); | ||
} | ||
@@ -70,2 +70,45 @@ | ||
function defineNotify(value: any, dep: Dep, notifyId: string) { | ||
if (isObject(value)) { | ||
if (!(value as any)[NOTIFY_KEY]) { | ||
Object.defineProperty(value, NOTIFY_KEY, { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: {}, | ||
}); | ||
} | ||
const notifyFns = (value as any)[NOTIFY_KEY]; | ||
const notifyFn = (n: any, o: any, options?: Partial<ChangeOptions>) => { | ||
dep.notifyChange(n, o, { | ||
type: 'notify', | ||
...(options || {}), | ||
}); | ||
}; | ||
notifyFns[notifyId] = notifyFn; | ||
} | ||
} | ||
function removeNotify(value: any, notifyId: string) { | ||
if (isObject(value)) { | ||
const notifyFns = (value as any)[NOTIFY_KEY]; | ||
if (notifyFns) { | ||
notifyFns[notifyId] = null; | ||
} | ||
} | ||
} | ||
function callNotifyFns(value: any, ...args: any[]) { | ||
if (isObject(value)) { | ||
const notifyFns = (value as any)[NOTIFY_KEY]; | ||
if (notifyFns) { | ||
for (const key in notifyFns) { | ||
if (notifyFns[key]) { | ||
notifyFns[key](...args); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function defineProperty(obj: IObservableObject, key: string): void; | ||
@@ -82,2 +125,4 @@ function defineProperty(obj: ObservableArray, key: number): void; | ||
const dep = new Dep(obj, key); | ||
const notifyId = genId(`notify-${key}-`); | ||
defineNotify(value, dep, notifyId); | ||
Object.defineProperty(obj, key, { | ||
@@ -90,16 +135,2 @@ configurable: typeof key === 'number', | ||
currentDepList.add(dep); | ||
if (isObject(value)) { | ||
Object.defineProperty(value, '__notify', { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: (n: any, o: any, options?: Partial<ChangeOptions>) => { | ||
dep.notifyChange(n, o, { | ||
type: 'notify', | ||
...(options || {}), | ||
}); | ||
}, | ||
}); | ||
} | ||
} | ||
@@ -117,2 +148,7 @@ | ||
if (oldValue !== value && isObject(oldValue)) { | ||
removeNotify(oldValue, notifyId); | ||
} | ||
defineNotify(value, dep, notifyId); | ||
if (!isSilentValue(v)) { | ||
@@ -178,6 +214,3 @@ dep.notifyChange(newValue, oldValue); | ||
obj[name] = silent ? silentValue(value) : value; | ||
const notify = (obj as any).__notify; | ||
if (notify) { | ||
notify(obj, obj); | ||
} | ||
callNotifyFns(obj, obj, obj); | ||
} else { | ||
@@ -184,0 +217,0 @@ obj[name] = silent ? silentValue(value) : value; |
@@ -25,10 +25,10 @@ import { isArray } from './utils'; | ||
export function unraw(obj: any) { | ||
export function unraw<T>(obj: T): T { | ||
return isRaw(obj) ? clone(obj) : obj; | ||
} | ||
export default function raw(obj: any) { | ||
export default function raw<T>(obj: T): T { | ||
const cloneObj = clone(obj); | ||
if (isObject(obj) && obj[RAW_KEY] !== RAW_FLAG) { | ||
if (isObject(obj) && (obj as any)[RAW_KEY] !== RAW_FLAG) { | ||
Object.defineProperty(cloneObj, RAW_KEY, { | ||
@@ -35,0 +35,0 @@ value: RAW_FLAG, |
@@ -29,3 +29,3 @@ import watchDeep from './watchDeep'; | ||
const watchKeys = (data: Record<string, any>) => { | ||
const stopList = watchDeep( | ||
const stop = watchDeep( | ||
data, | ||
@@ -38,3 +38,3 @@ setData, | ||
); | ||
return stopList; | ||
return [stop]; | ||
}; | ||
@@ -41,0 +41,0 @@ |
@@ -10,5 +10,5 @@ export interface IIndexedObject { | ||
let id = 0; | ||
export function genId() { | ||
export function genId(prefix = 'g-') { | ||
id += 1; | ||
return id; | ||
return `${prefix}${id}`; | ||
} |
import { call, getCurrent, IErrorCallback, ChangeOptions } from './dep'; | ||
import { isArray } from './utils'; | ||
import { isObject } from '@goldfishjs/utils'; | ||
import isRaw from './isRaw'; | ||
@@ -27,3 +28,3 @@ export type Unwatch = () => void; | ||
private removeListenersGroup: Function[][] = []; | ||
private removeListeners: Function[] = []; | ||
@@ -44,10 +45,16 @@ constructor( | ||
this.isStopped = true; | ||
this.removeListenersGroup.forEach((group) => { | ||
group.forEach(fn => fn()); | ||
}); | ||
this.removeListenersGroup = []; | ||
this.callRemoveListeners(); | ||
} | ||
private callRemoveListeners() { | ||
this.removeListeners.forEach(r => r()); | ||
this.removeListeners.splice(0, this.removeListeners.length); | ||
} | ||
// 递归访问一下,方便搜集依赖 | ||
private deepVisit(obj: any) { | ||
if (obj && isRaw(obj)) { | ||
return; | ||
} | ||
if (isObject(obj)) { | ||
@@ -77,4 +84,5 @@ for (const key in obj) { | ||
} | ||
this.removeListenersGroup.push( | ||
getCurrent().addChangeListener((n: any, o: any, options: ChangeOptions) => { | ||
this.removeListeners.push( | ||
...getCurrent().addChangeListener((n: any, o: any, options: ChangeOptions) => { | ||
this.callRemoveListeners(); | ||
if (this.isStopped) { | ||
@@ -81,0 +89,0 @@ return; |
@@ -1,4 +0,7 @@ | ||
import watch, { IWatchOptions } from './watch'; | ||
import { ChangeOptions } from './dep'; | ||
import { IWatchOptions } from './watch'; | ||
import { ChangeOptions, getCurrent } from './dep'; | ||
import { isObject } from '@goldfishjs/utils'; | ||
import isRaw from './isRaw'; | ||
import generateKeyPathString from './generateKeyPathString'; | ||
import { call } from './dep'; | ||
@@ -10,5 +13,49 @@ export interface IWatchDeepCallback { | ||
export interface IWatchDeepOptions extends Omit<IWatchOptions, 'deep'> { | ||
customWatch?: typeof watch; | ||
result?: any; | ||
} | ||
type KeyPathList = (string | number)[]; | ||
class StopFns { | ||
private fns: Record<string, Record<string, () => void>> = {}; | ||
public add(keyPathList: KeyPathList, curKey: string | number, fn: () => void) { | ||
const keyPathString = generateKeyPathString(keyPathList); | ||
if (!this.fns[keyPathString]) { | ||
this.fns[keyPathString] = {}; | ||
} | ||
if (this.fns[keyPathString][curKey]) { | ||
throw new Error(`Duplicate stop function for key: ${generateKeyPathString([...keyPathList, curKey])}`); | ||
} | ||
this.fns[keyPathString][curKey] = fn; | ||
} | ||
public remove(keyPathList: KeyPathList) { | ||
const keyPathString = generateKeyPathString(keyPathList); | ||
for (const kps in this.fns) { | ||
if (kps.indexOf(keyPathString) === 0) { | ||
for (const k in this.fns[kps]) { | ||
this.fns[kps][k](); | ||
} | ||
this.fns[kps] = {}; | ||
} | ||
} | ||
} | ||
private callAll() { | ||
for (const k1 in this.fns) { | ||
for (const k2 in this.fns[k1]) { | ||
this.fns[k1][k2](); | ||
} | ||
} | ||
} | ||
public removeAll() { | ||
this.callAll(); | ||
this.fns = {}; | ||
} | ||
} | ||
class Watcher { | ||
@@ -19,4 +66,6 @@ private obj: any; | ||
private callback?: IWatchDeepCallback; | ||
private callback: IWatchDeepCallback; | ||
private stopFns = new StopFns(); | ||
public constructor( | ||
@@ -32,45 +81,59 @@ obj: any, | ||
private iterate(curObj: any, keyPathList: (string | number)[] = []) { | ||
const baseWatch = this.options && this.options.customWatch || watch; | ||
const stopList: (() => void)[] = []; | ||
if (curObj && typeof curObj === 'object') { | ||
const iterate = (key: string | number) => { | ||
const keyStopList: Function[] = []; | ||
stopList.push(baseWatch( | ||
() => curObj[key], | ||
(newV, oldV, options) => { | ||
this.callback && this.callback(this.obj, [...keyPathList, key], newV, oldV, options); | ||
private watchObj(obj: any, keyPathList: KeyPathList) { | ||
if (isRaw(obj)) { | ||
return; | ||
} | ||
// If the old value is an object, then clear all children watchers on it. | ||
if (isObject(oldV)) { | ||
keyStopList.forEach(stop => stop()); | ||
keyStopList.splice(0, keyStopList.length); | ||
} | ||
this.stopFns.remove(keyPathList); | ||
if (Array.isArray(obj)) { | ||
obj.forEach((_, index) => { | ||
this.watchSingleKey(obj, index, keyPathList); | ||
}); | ||
} else if (isObject(obj)) { | ||
for (const key in obj) { | ||
this.watchSingleKey(obj, key, keyPathList); | ||
} | ||
} | ||
} | ||
// If the new value is an object, then set watchers on it's children. | ||
if (isObject(newV)) { | ||
keyStopList.push(...this.iterate(newV, [...keyPathList, key])); | ||
} | ||
private watchSingleKey(obj: any, key: string | number, keyPathList: KeyPathList) { | ||
const nextKeyPathList = [...keyPathList, key]; | ||
call( | ||
() => { | ||
obj[key]; | ||
const stopList = getCurrent().addChangeListener( | ||
(newV, oldV, options) => { | ||
this.watchObj(newV, nextKeyPathList); | ||
this.callback( | ||
this.obj, | ||
nextKeyPathList, | ||
newV, | ||
oldV, | ||
options, | ||
); | ||
}, | ||
this.options, | ||
)); | ||
if (!this.options || !this.options.immediate) { | ||
keyStopList.push(...this.iterate(curObj[key], [...keyPathList, key])); | ||
false, | ||
); | ||
this.stopFns.add(keyPathList, key, () => stopList.forEach(s => s())); | ||
}, | ||
(e) => { | ||
if (this.options?.onError) { | ||
this.options.onError(e); | ||
} else { | ||
throw e; | ||
} | ||
}; | ||
}, | ||
); | ||
this.watchObj(obj[key], nextKeyPathList); | ||
} | ||
if (Array.isArray(curObj)) { | ||
curObj.forEach((_, index) => iterate(index)); | ||
} else { | ||
for (const key in curObj) { | ||
iterate(key); | ||
} | ||
} | ||
public watch() { | ||
if (this.options?.immediate) { | ||
this.callback(this.obj, [], this.obj, undefined); | ||
} | ||
return stopList; | ||
return this.watchObj(this.obj, []); | ||
} | ||
public watch() { | ||
return this.iterate(this.obj); | ||
public stop() { | ||
this.stopFns.removeAll(); | ||
} | ||
@@ -85,3 +148,4 @@ } | ||
const watcher = new Watcher(obj, callback, options); | ||
return watcher.watch(); | ||
watcher.watch(); | ||
return () => watcher.stop(); | ||
} |
@@ -24,3 +24,3 @@ import { Methods } from './observable'; | ||
add(dep: Dep): void; | ||
addChangeListener(cb: (n: any, o: any, options: ChangeOptions) => void, shouldBatch?: boolean): Function[]; | ||
addChangeListener(cb: (n: any, o: any, options: ChangeOptions) => void, shouldBatch?: boolean): (() => void)[]; | ||
} | ||
@@ -31,3 +31,9 @@ export declare function getCurrent(): DepList; | ||
} | ||
/** | ||
* Collect dependencies in `fn`. | ||
* | ||
* @param fn | ||
* @param errorCb | ||
*/ | ||
export declare function call(fn: Function, errorCb?: IErrorCallback): void; | ||
export {}; |
@@ -22,1 +22,2 @@ export { default as observable } from './observable'; | ||
export { default as markObservable } from './markObservable'; | ||
export { default as call } from './call'; |
export declare function isRaw(obj: any): boolean; | ||
export declare function unraw(obj: any): any; | ||
export default function raw(obj: any): any; | ||
export declare function unraw<T>(obj: T): T; | ||
export default function raw<T>(obj: T): T; |
@@ -10,8 +10,8 @@ declare const SILENT_KEY = "__silent__"; | ||
} | { | ||
[SILENT_KEY]: {}; | ||
__silent__: {}; | ||
value: any; | ||
})[] | { | ||
[SILENT_KEY]: {}; | ||
__silent__: {}; | ||
value: any; | ||
} | undefined; | ||
export {}; |
@@ -5,2 +5,2 @@ export interface IIndexedObject { | ||
export declare function isArray(v: any): v is any[]; | ||
export declare function genId(): number; | ||
export declare function genId(prefix?: string): string; |
@@ -1,2 +0,2 @@ | ||
import watch, { IWatchOptions } from './watch'; | ||
import { IWatchOptions } from './watch'; | ||
import { ChangeOptions } from './dep'; | ||
@@ -7,4 +7,4 @@ export interface IWatchDeepCallback { | ||
export interface IWatchDeepOptions extends Omit<IWatchOptions, 'deep'> { | ||
customWatch?: typeof watch; | ||
result?: any; | ||
} | ||
export default function watchDeep(obj: any, callback: IWatchDeepCallback, options?: IWatchDeepOptions): (() => void)[]; | ||
export default function watchDeep(obj: any, callback: IWatchDeepCallback, options?: IWatchDeepOptions): () => void; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
61911
17
67
2031
1
+ Added@goldfishjs/module-usage@1.9.6(transitive)
+ Added@goldfishjs/utils@1.9.6(transitive)
+ Added@types/lodash@4.17.13(transitive)
+ Added@types/lodash-es@4.17.12(transitive)
+ Addedlodash-es@4.17.21(transitive)
- Removed@goldfishjs/utils@0.0.18(transitive)
- Removedlodash.omit@4.5.0(transitive)
- Removedlodash.pick@4.4.0(transitive)
Updated@goldfishjs/utils@^1.0.0