2.0.8 • Published 11 months ago

watchable-proxy v2.0.8

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

watchable-proxy

Convert object to proxy that can be watched deeply and conditionally

usage

import { watchable, watch } form 'watchable-proxy';
// support commonjs require
// const { watchable, watch } = require('watchable-proxy');

const proxy = watchable({
  a: {
    b: {
      c: 10
    }
  }
});

// the second param can be String、RegExp、(String|RegExp)[], 
// it's for matching the “path” prop in callback
const dispose = watch(proxy, ['a.b.c'], ({ path, oldVal, newVal, type, paths }) => {
  console.log({
    // when execute "proxy.a.b.c = 20", callback called, props are:
 		path,   // 'a.b.c'
    oldVal, // 10
    newVal, // 20
    type,   // 'SET' (type includes 'SET' | 'ADD' | 'DEL' | batchName )
    paths,  // ['a', 'b', 'c']
    matchedIndex, // 0
    matchedRule,  // 'a.b.c',
    target, // the direct object triggering the set
  })
  
  console.log(proxy.a.b.c) // 10
  // the function  will called after set
  // you can get the changed object
  return () => {
    console.log(proxy.a.b.c) // 20
  }
});

proxy.a.b.c = 20;

// if you don't want watch any more, do dispose
dispose();

watchGet

const proxy = watchable({ a: 10, b: { c: 'foo' } });
watchGet(proxy, ({ path }) => {
  console.log('detect get', path) // 'a'
})
console.log(proxy.a) // 10

fuzzy match

const proxy = watchable({
  a: {
    b: {
      c: 10,
      d: 20,
    },
  },
});

// watch any prop of “b” change
watch(proxy, ['a.b.*'], fn1);
function fn1() { }
proxy.a.b.c = 20; // fn1 called
proxy.a.b.d = 20; // fn1 called

// watch any props or subProps of “a” change
watch(proxy, ['a.**'], fn2);
function fn2() { }
proxy.a.b.c = 30;   // fn2 called
proxy.a.x = 30;     // fn2 called (type -> 'ADD')

watch array

fuzzy match array

// watch any set or delete of array
const arr = watchable([ { foo: 0 }, 1 ]);
function fn1() { }
watch(arr, ['*n', '*n.**'], fn1);
arr[1] = 'baz';    // fn1 called, match '*n'
delete arr[1];     // fn1 called
arr[0].foo = 'bar' // fn1 called, match '*n.**'

watch array's in place method

We give additional in place methods:filterSelf , mapSelf , sliceSelf

import { BATCH, BatchOpt } from 'watchable-proxy';
// watch push method, 
watch(arr, [BATCH], fn2);
function fn2({
	path,   // BATCH -> '__$_batch'
	oldVal, // [{ foo: 0 }, 1] -> Array
	newVal, // [{ foo: 0 }, 1, 2, 3] -> Proxy
	type,   // 'push'
	paths,  // [BATCH]
	matchedIndex, // 0
	matchedRule,  // BATCH
}) { }
arr.push(2, 3); // fn2 call 1 times

// watch single set in push
watch(arr, ['*n'], fn3);
function fn3({}) {
  // got below two set action from push
  // 1. arr[2] = 2
  // 2. arr[3] = 3
}
// fn3 call 2 times
// in 'setter' way, BATCH watcher won't trigger
arr.push(2, 3, BatchOpt({ triggerTarget: 'setter' })); 

regexp match

const proxy = watchable({
  a: {
    b: {
      c: 10,
      d: 20,
    },
  },
});

// if “/a\.b\.[^\.]+/.test(path)” is true , fn1 will be called
watch(proxy, [/a\.b\.[^\.]+/], fn1);
function fn1() { }

proxy.a.b.c = 20; // fn1 called
proxy.a.b.d = 30; // fn1 called

Scope api , batch cancel watch

const scope = new Scope();
const p = watchable({ a: 10 });
function fn1() { }
function fn2() { }
scope.watch(p, fn1);
scope.watch(p, fn2);
p.a = 20; // fn1, fn2 called

scope.dispose();
p.a = 30; // fn1, fn2 won't call

setProp api , custom set action

const p = watchable({ value: 10 });

function watcher() { }
watch(p, watcher);

// use noTriggerWatcher the watcher will no be trigger this time
setProp(p, 'value', 10, { noTriggerWatcher: true });

// use info options to pass info to watcher's props.info
setProp(p, 'value', 10, { info: 'hello' });

// use withoutWatchTrain make the parent watcher can't catch the value's props change
const value = { a: 10 };
setProp(p, 'value', value, { withoutWatchTrain: true });

batchSet api , merge setters into a watchable batch

Notice❗️setProp Api's priority is higher than batchSet. Means if an setProp invoke in batchSet, whether setterWatcher will trigger depends on setProp.

import { BATCH, batchSet } from 'watchable-proxy';
const p = watchable({ 
  value: 10, 
  sub: {
		str: 'foo',    
  },
});

const handleP = batchSet(() => {
  p.value = 20;
  p.sub.str = 'baz';
}, {
  // in this function changes of p and p's subProp
  // will be merged
  proxies: [p]
});

function batchWatcher() { }
watch(p, BATCH, batchWatcher);

function setterWatcher() { }
watch(p, ['value', 'sub.str'], setterWatcher);

// batchWatcher call 1 times
// setterWatcher call 0 times
handleP();

cloneWatchable and cloneRaw api

const p = watchable({
  a: 10,
  b: { v: 20 }
})

// deepCone, got a new irrelevant proxy 
const cloned = cloneWatchable(p);

// deepCone, got a new irrelevant raw object 
const clonedRaw = cloneRaw(p);

"watchable proxy" specific

watch share and watch pass

const a = watchable({ });
const b = watchable({ });
const c = watchable({ value: 10 });

a.c = c;
b.c = c;

watch(a, ['c'], aWatcher);
function aWatcher() { }

watch(b, ['c'], bWatcher);
function bWatcher() { }

// both aWatcher and bWatcher will be called
c.value = 20

circular reference

when raw object has circular reference, the parent("c") can't watch changes of the circular ref("a")

// a -> b -> c -> a  ( circular ref  )
//      | -> d

const rawA: any = {
  b: {
    c: {},
    d: 'd'
  }
};
rawA.b.c.a = rawA;

const a = watchable(rawA);
function aWatcher() { }
watch(a, aWatcher);

const c = a.b.c;
function cWatcher() { }
watch(c, cWatcher);

// aWatcher will be called, 
// but cWatcher will not be called 
// even d is a child node of c by circular ref
a.b.c.d = 'joker';

manually make circular ref after make watchable

  1. use assignment expression, circular node's change will trigger watchers all the way up until the node been walked;
  2. use setProp api,will work like above raw circular ref case
// a -> b -> c -> a  ( circular ref  )
//      | -> d

const a = watchable({
  b: {
    c: {},
    d: 'd'
  }
});
const c = a.b.c;

// manually make circular ref by assignment expression
// c.a = a;

// manually make circular ref by setProp api
setProp(c, 'a', a, { withoutWatchTrain: true });

function aWatcher() { }
watch(a, aWatcher);

function cWatcher() { }
watch(c, cWatcher);


// aWatcher will be called normally
// use assignment expression : cWatcher will  be called
//           use setProp api : cWatcher won't be called
a.b.c.d = 'joker';

specific getter

const p = watchable({ a: { value: 10 } });

const { a } = p;
// get raw object of a
// often used to get raw object which created by system class
a['__$_raw']             
a['__$_parents']         // get "[p]"
a['__$_isObservableObj'] // get "true"

"this" of a function property

"this" of array's method

// if the method is belong to an array,
// "this" will always bind to "proxy receiver" 
// to make sure array's operator like "push" "splice"
// can be watched correctly;
const arr =  watchable([0,1,2]);
push = arr.push;

function watcher() { }
watch(arr, ['__$_batch'], watcher);

// watcher can be triggered
push(3);

"this" of function belongs to a literal object or instance of a class

class A {
  value= 10;
  getSum(v: number = 0) {
    return this.value + v
  }
}

const rawA = new A();
const a = watchable(rawA);

const { getSum } = a;
getSum(1) // return 11

// use fn.call, apply and bind as well
const b = { value: 20 };
getSum.call(b, 1); // return 21
2.0.3

1 year ago

2.0.2

1 year ago

2.0.5

11 months ago

2.0.4

1 year ago

2.0.7

11 months ago

2.0.6

11 months ago

2.0.8

11 months ago

2.0.1

1 year ago

1.0.12

1 year ago

1.0.11

1 year ago

1.0.10

1 year ago

1.0.9

1 year ago

1.0.8

1 year ago

1.0.7

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.2

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.1

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago