Comparing version 1.1.0 to 1.2.0
@@ -1,11 +0,85 @@ | ||
/** | ||
* Watch an object or array for changes. It works recursively, so it will even detect if you modify a deep property like `obj.a.b[0].c = true`. | ||
* | ||
* @param object - Object to watch for changes. | ||
* @param onChange - Function that gets called anytime the object changes. | ||
* @returns A version of `object` that is watched. It's the exact same object, just with some `Proxy` traps. | ||
*/ | ||
export default function onChange<ObjectType extends {[key: string]: unknown}>( | ||
object: ObjectType, | ||
onChange: () => void | ||
): ObjectType; | ||
declare const onChange: { | ||
/** | ||
Watch an object or array for changes. It works recursively, so it will even detect if you modify a deep property like `obj.a.b[0].c = true`. | ||
@param object - Object to watch for changes. | ||
@param onChange - Function that gets called anytime the object changes. | ||
@returns A version of `object` that is watched. It's the exact same object, just with some `Proxy` traps. | ||
@example | ||
``` | ||
import onChange = require('on-change'); | ||
const object = { | ||
foo: false, | ||
a: { | ||
b: [ | ||
{ | ||
c: false | ||
} | ||
] | ||
} | ||
}; | ||
let i = 0; | ||
const watchedObject = onChange(object, function (path, value, previousValue) { | ||
console.log('Object changed:', ++i); | ||
console.log('this:', this); | ||
console.log('path:', path); | ||
console.log('value:', value); | ||
console.log('previousValue:', previousValue); | ||
}); | ||
watchedObject.foo = true; | ||
//=> 'Object changed: 1' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: false | ||
// } | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "foo"' | ||
//=> 'value: true' | ||
//=> 'previousValue: false' | ||
watchedObject.a.b[0].c = true; | ||
//=> 'Object changed: 2' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: true | ||
// } | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "a.b.0.c"' | ||
//=> 'value: true' | ||
//=> 'previousValue: false' | ||
``` | ||
*/ | ||
<ObjectType extends {[key: string]: unknown}>( | ||
object: ObjectType, | ||
onChange: ( | ||
this: ObjectType, | ||
path: string, | ||
value: unknown, | ||
previousValue: unknown | ||
) => void | ||
): ObjectType; | ||
// TODO: Remove this for the next major release, refactor the whole definition to: | ||
// declare function onChange<ObjectType extends {[key: string]: unknown}>( | ||
// object: ObjectType, | ||
// onChange: (this: ObjectType, path: string, value: unknown, previousValue: unknown) => void | ||
// ): ObjectType; | ||
// export = onChange; | ||
default: typeof onChange; | ||
}; | ||
export = onChange; |
61
index.js
@@ -5,2 +5,14 @@ 'use strict'; | ||
const concatPath = (path, property) => { | ||
if (property && property.toString) { | ||
if (path) { | ||
path += '.'; | ||
} | ||
path += property.toString(); | ||
} | ||
return path; | ||
}; | ||
const proxyTarget = Symbol('ProxyTarget'); | ||
@@ -12,6 +24,7 @@ | ||
const propCache = new WeakMap(); | ||
const pathCache = new WeakMap(); | ||
const handleChange = () => { | ||
const handleChange = (path, property, previous, value) => { | ||
if (!inApply) { | ||
onChange(); | ||
onChange.call(proxy, concatPath(path, property), value, previous); | ||
} else if (!changed) { | ||
@@ -23,14 +36,17 @@ changed = true; | ||
const getOwnPropertyDescriptor = (target, property) => { | ||
if (!propCache.has(target)) { | ||
propCache.set(target, new Map()); | ||
let props = propCache.get(target); | ||
if (props) { | ||
return props; | ||
} | ||
const props = propCache.get(target); | ||
if (props.has(property)) { | ||
return props.get(property); | ||
props = new Map(); | ||
propCache.set(target, props); | ||
let prop = props.get(property); | ||
if (!prop) { | ||
prop = Reflect.getOwnPropertyDescriptor(target, property); | ||
props.set(property, prop); | ||
} | ||
const prop = Reflect.getOwnPropertyDescriptor(target, property); | ||
props.set(property, prop); | ||
return prop; | ||
@@ -40,8 +56,7 @@ }; | ||
const invalidateCachedDescriptor = (target, property) => { | ||
if (!propCache.has(target)) { | ||
return; | ||
const props = propCache.get(target); | ||
if (props) { | ||
props.delete(property); | ||
} | ||
const props = propCache.get(target); | ||
props.delete(property); | ||
}; | ||
@@ -72,2 +87,3 @@ | ||
pathCache.set(value, concatPath(pathCache.get(target), property)); | ||
return new Proxy(value, handler); | ||
@@ -81,7 +97,7 @@ }, | ||
const previous = Reflect.get(target, property, value, receiver); | ||
const previous = Reflect.get(target, property, receiver); | ||
const result = Reflect.set(target, property, value); | ||
if (previous !== value) { | ||
handleChange(); | ||
handleChange(pathCache.get(target), property, previous, value); | ||
} | ||
@@ -96,3 +112,3 @@ | ||
handleChange(); | ||
handleChange(pathCache.get(target), property, undefined, descriptor.value); | ||
@@ -103,6 +119,7 @@ return result; | ||
deleteProperty(target, property) { | ||
const previous = Reflect.get(target, property); | ||
const result = Reflect.deleteProperty(target, property); | ||
invalidateCachedDescriptor(target, property); | ||
handleChange(); | ||
handleChange(pathCache.get(target), property, previous); | ||
@@ -132,6 +149,10 @@ return result; | ||
return new Proxy(object, handler); | ||
pathCache.set(object, ''); | ||
const proxy = new Proxy(object, handler); | ||
return proxy; | ||
}; | ||
module.exports = onChange; | ||
// TODO: Remove this for the next major release | ||
module.exports.default = onChange; |
{ | ||
"name": "on-change", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Watch an object or array for changes", | ||
@@ -16,3 +16,3 @@ "license": "MIT", | ||
"scripts": { | ||
"test": "xo && ava && tsd-check", | ||
"test": "xo && ava && tsd", | ||
"bench": "matcha bench/bench.js" | ||
@@ -41,7 +41,7 @@ }, | ||
"devDependencies": { | ||
"ava": "^1.0.1", | ||
"ava": "^1.4.1", | ||
"matcha": "^0.7.0", | ||
"tsd-check": "^0.3.0", | ||
"tsd": "^0.7.1", | ||
"xo": "^0.24.0" | ||
} | ||
} |
@@ -34,4 +34,8 @@ # on-change [![Build Status](https://travis-ci.org/sindresorhus/on-change.svg?branch=master)](https://travis-ci.org/sindresorhus/on-change) | ||
let i = 0; | ||
const watchedObject = onChange(object, () => { | ||
const watchedObject = onChange(object, function (path, value, previousValue) { | ||
console.log('Object changed:', ++i); | ||
console.log('this:', this); | ||
console.log('path:', path); | ||
console.log('value:', value); | ||
console.log('previousValue:', previousValue); | ||
}); | ||
@@ -41,5 +45,31 @@ | ||
//=> 'Object changed: 1' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: false | ||
// } | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "foo"' | ||
//=> 'value: true' | ||
//=> 'previousValue: false' | ||
watchedObject.a.b[0].c = true; | ||
//=> 'Object changed: 2' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: true | ||
// } | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "a.b.0.c"' | ||
//=> 'value: true' | ||
//=> 'previousValue: false' | ||
``` | ||
@@ -66,3 +96,10 @@ | ||
The function receives three arguments: | ||
1. A path to the value that was changed. A change to `c` in the above example would return `a.b.0.c`. | ||
2. The new value at the path. | ||
3. The previous value at the path. | ||
The context (this) is set to the original object passed to `onChange` (with Proxy). | ||
## Use-case | ||
@@ -69,0 +106,0 @@ |
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
9848
188
162