Last active
September 21, 2020 05:39
-
-
Save alshdavid/ed59076064bb975cbd8ff36a50a28784 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Reactive={};(()=>{const e="[[ReactiveState]]",t=e=>{e.o.n();for(const r of e.c)t(r)},r=(e,t,r)=>Array.isArray(t)?s(e,t,r):o(e,t,r),s=(e,r,n)=>new Proxy(r,{get:(t,r)=>Array.isArray(t[r])?s(e,t[r],n):"object"==typeof t[r]?o(e,t[r],n):t[r],set:(e,r,s)=>e[r]===s||(e[r]=s,t(n),!0)}),o=(r,n,i)=>{const c={};for(const f in n)if(f!==e&&"toJSON"!==f){if(n[f][e])n[f][e].c.push(i);else if(Array.isArray(n[f]))n[f]=s(r,n[f],i);else if("object"==typeof n[f])n[f]=o(r,n[f],i);else if(i.p.includes(f))continue;c[f]=n[f],Object.defineProperty(n,f,{enumerable:!0,get:()=>c[f],set:e=>c[f]===e||(c[f]=e,t(i),!0)}),i.p.push(f)}return n};Reactive.observe=((t,r,s=[])=>{const o=[];for(const e of s)o.push(e());const n=t[e].o.s(()=>{if(0!==s.length)for(let e=0;e<s.length;e++){const n=s[e]();if(o[e]!==n)return o[e]=n,void r(t)}else r(t)});return()=>n.u()}),Reactive.create=(t=>{if(t[e])return r(t,t,t[e]);const s={o:new class{constructor(){this._s=[]}s(e){return this._s.push(e),{u:()=>this._ss=this._s.filter(t=>t!==e)}}n(e){for(const t of this._s)t(e)}},c:[],p:[]},o=r(t,t,s);return Object.defineProperty(o,e,{enumerable:!1,value:s}),Object.defineProperty(o,"toJSON",{enumerable:!1,value:function(){var t={};for(var r in this)r!==e&&(t[r]=this[r]);return t}}),o})})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Callback<T = any> = (value: T) => void | |
export interface Subscription { | |
unsubscribe: () => void | |
} | |
export class Subject<T> { | |
private subscribers: Callback<T>[] = [] | |
subscribe(cb: Callback<T>): Subscription { | |
this.subscribers.push(cb) | |
return { | |
unsubscribe: () => this.subscribers = this.subscribers.filter(v => v !== cb) | |
} | |
} | |
next(value: T) { | |
for (const cb of this.subscribers) { | |
cb(value) | |
} | |
} | |
} | |
export const KEY = '[[ReactiveState]]' | |
export type State = { | |
onChange: Subject<void> | |
children: Array<State> | |
patched: string[] | |
} | |
export const create = <T,>(source: T): T => { | |
if ((source as any)[KEY]) { | |
return watch(source, source, (source as any)[KEY]) | |
} | |
const state: State = { | |
onChange: new Subject(), | |
children: [], | |
patched: [] | |
} | |
const _source: any = watch(source, source, state) | |
Object.defineProperty(_source, KEY, { | |
enumerable: false, | |
value: state | |
}) | |
Object.defineProperty(_source, 'toJSON', { | |
enumerable: false, | |
value: function () { | |
var result: any = {}; | |
for (var x in this) { | |
if (x !== KEY) { | |
result[x] = this[x]; | |
} | |
} | |
return result; | |
} | |
}) | |
return _source | |
} | |
const notify = (state: State) => { | |
state.onChange.next() | |
for (const child of state.children) { | |
notify(child) | |
} | |
} | |
const watch = ( | |
rootSource: any, | |
source: any, | |
node: State, | |
): any => { | |
if (Array.isArray(source)) { | |
return watchArray(rootSource, source, node) | |
} | |
return watchObject(rootSource, source, node) | |
} | |
const watchArray = ( | |
rootSource: any, | |
source: any, | |
node: State, | |
):any => { | |
const result = new Proxy<any>(source, { | |
get(target, prop) { | |
if (Array.isArray(target[prop])) { | |
return watchArray(rootSource, target[prop], node) | |
} else if (typeof target[prop] === 'object') { | |
return watchObject(rootSource, target[prop], node) | |
} | |
return target[prop] | |
}, | |
set(target, propKey, value) { | |
if (target[propKey] === value) { | |
return true; | |
} | |
target[propKey] = value; | |
notify(node) | |
return true | |
}, | |
}) | |
return result | |
} | |
const watchObject = ( | |
rootSource: any, | |
source: any, | |
node: State, | |
): any => { | |
const proxy: any = {} | |
for (const key in source) { | |
if (key === KEY || key === 'toJSON') { | |
continue | |
} | |
if (source[key][KEY]) { | |
(source[key][KEY] as State).children.push(node) | |
} else if (Array.isArray(source[key])) { | |
source[key] = watchArray(rootSource, source[key], node) | |
} else if (typeof source[key] === 'object') { | |
source[key] = watchObject(rootSource, source[key], node) | |
} else if (node.patched.includes(key)) { | |
continue | |
} | |
proxy[key] = source[key] | |
Object.defineProperty(source, key, { | |
enumerable: true, | |
get: () => { | |
return proxy[key] | |
}, | |
set: (value) => { | |
if (proxy[key] === value) { | |
return true | |
} | |
proxy[key] = value | |
notify(node) | |
return true | |
} | |
}) | |
node.patched.push(key) | |
} | |
return source | |
} | |
export type DisposeFn = () => void | |
export const observe = <T,>( | |
source: T, | |
cb: (value: T) => void, | |
watch: Array<() => any> = [] | |
): DisposeFn => { | |
const watchCache: any[] = [] | |
for (const cb of watch) { | |
watchCache.push(cb()) | |
} | |
const _source: any = source | |
const state: State = _source[KEY] | |
const subscription = state.onChange.subscribe(() => { | |
if (watch.length === 0) { | |
cb(source) | |
return | |
} | |
for (let i = 0; i < watch.length; i++) { | |
const update = watch[i]() | |
if (watchCache[i] !== update) { | |
watchCache[i] = update | |
cb(source) | |
return | |
} | |
} | |
}) | |
return () => subscription.unsubscribe() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Reactive from '@alshdavid/reactive' | |
class Foo { | |
bar = 0 | |
} | |
const foo = Reactive.create(new Foo()) | |
Reactive.observe(foo, console.log) | |
foo.bar = 1 | |
setTimeout(() => foo.bar = 2, 1000) | |
setTimeout(() => foo.bar = 3, 2000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useMemo, useState } from 'react'; | |
import Reactive from '@alshdavid/reactive' | |
export function useViewModel<T,>( | |
ctor: () => T, | |
watch: Array<() => any> = [], | |
): T { | |
const [, forceUpdate] = useState(false); | |
const vm = useMemo(() => Reactive.create(ctor()), []) | |
useEffect(() => Reactive.observe(vm, () => forceUpdate(s => !s), watch)) | |
return vm | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment