-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreateState.ts
72 lines (63 loc) · 1.99 KB
/
createState.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { bindAll } from './bindAll';
import { clone } from './clone';
import { createEmitter } from './createEmitter';
import { isObject } from './isObject';
import { omit } from './omit';
import { guid } from './random';
import { Obj } from './types';
type State<T> = T & {
dispose(fn: (s: T) => void): void;
observe(fn: (s: T) => void): () => void;
};
const { emit, on } = createEmitter();
const { defineProperty, getOwnPropertyDescriptor } = Object;
function handler(uid: string) {
return {
get(obj: Obj, key: string) {
if (key === '_isProxy') return true;
const d = getOwnPropertyDescriptor(obj, key);
const nok = isObject(obj[key]) && !obj[key]._isProxy && d?.writable;
nok && (obj[key] = new Proxy(obj[key], handler(uid)));
return obj[key];
},
set(obj: Obj, key: string, val: unknown) {
if (obj[key] !== val) (obj[key] = val), emit(`update:${uid}`);
return true;
},
deleteProperty(obj: Obj, key: string) {
delete obj[key];
emit(`update:${uid}`);
return true;
},
};
}
type Options = {
immediate?: boolean;
};
export function createState<T extends object>(data?: T, opts?: Options): State<T> {
const { immediate } = opts || {};
const fns = <Function[]>[];
const init = clone(data || {}) as Obj;
const uid = guid();
defineProperty(init, 'dispose', {
value(fn: Function) {
const idx = fns.findIndex((f) => f === fn);
idx !== -1 && fns.splice(idx, 1);
},
});
defineProperty(init, 'observe', {
value(fn: Function) {
if (!fns.includes(fn)) fns.push(fn);
return () => init.dispose(fn);
},
});
let nextTickId = 0;
const state = new Proxy(init, handler(uid));
on(`update:${uid}`, () => {
const data = omit(['dispose', 'observe'], state);
if (immediate) return fns.forEach((fn) => fn(data));
if (nextTickId) cancelAnimationFrame(nextTickId);
nextTickId = requestAnimationFrame(() => fns.forEach((fn) => fn(data)));
});
return bindAll(state) as State<T>;
}