@electerm/use-proxy v0.10.0-beta.0
useProxy
This is a fork of https://github.com/tylerlong/use-proxy, remove support for child operations, means only support set/get the whole object, operation like obj.x,y or obj[0] not supported, expecting to gain more performance when facing complicated object/array and reduce the use of event emiiters.
Installation
yarn add @electerm/use-proxy
# npm
npm i -D @electerm/use-proxyUsage
import {useProxy} from '@electerm/use-proxy';
import {Component} from '@electerm/use-proxy/build/react';
class Store {
count = 0;
todos = []; // a big array with 1000+ members and complicated props
increase() {
this.count += 1;
}
updateTodo (id, update) {
const arr = this.todos()
const inst = arr.find(f => f.id === id)
Object.assign(inst, update)
this._todo = JSON.stringify(arr)
}
setTodos (todos) {
this.todos = todos
}
}
const store = useProxy(new Store());
class App extends Component<{store: Store}> {
render() {
const store = this.props.store;
return (
<div>
<span>{store.count}</span>
<button onClick={() => store.increase()}>+</button>
</div>
);
}
}Event Emitter
import {useProxy} from '@tylerlong/use-proxy';
import {ProxyEvent} from '@tylerlong/use-proxy/build/models';
class Store {}
const store = useProxy(new Store());store.__emitter__ is an EventEmitter which will emit events about read/write to store. You can subscribe to events:
store.__emitter__.on('event', (event: ProxyEvent) => {
// do something with event
});Utility methods
run
The signature of run is
function run<T>(
proxy: ProxyType<T>,
func: Function
): [result: any, isTrigger: (event: ProxyEvent) => boolean]proxyis generated fromuseProxymethod:const proxy = useProxy(store).funcis a function which readsproxy.resultis the result offunc().isTriggeris a function which returnstrueif aneventwill "trigger"func()to have a different result.- when it returns true, most likely it's time to run
func()again(because you will get a different result from last time).
- when it returns true, most likely it's time to run
When you invoke run(proxy, func), func() is invoked immediately.
You can subscribe to proxy.__emitter__ and filter the events using isTrigger to get the trigger events (to run func() again).
For a sample usage of run, please check ./src/react.ts.
Another example is the implementation of the autoRun utility method. You may find it in ./src/index.ts.
autoRun
The signature of autoRun is
function autoRun<T>(
proxy: ProxyType<T>,
func: () => void,
decorator?: (func: () => void) => () => void
): {start: () => void; stop: () => void}proxyis generated fromuseProxymethod:const proxy = useProxy(store).funcis a function which readsproxy.decoratoris a method to change run schedule offunc, for example:func => _.debounce(func, 10, {leading: true, trailing: true})startandstopis to start and stopautoRun.
When you invoke start(), func() is invoked immediately.
func() will be invoked automatically afterwards if there are trigger events from proxy which change the result of func().
Invoke stop to stop autoRun.
For sample usages of autoRun, please check ./test/autoRun.spec.ts.
Known issue
- It only monitors
getandsetof properties. It doesn't monitordelete,hasandkeys. Because in 99.9% cases,get&setare sufficient to monitor and manage data. - You cannot proxy some built-in objects, such as
Set&Map. runandautoRunonly support sync methods. for async methods, make sure that the async part is irrelevant because it won't be monitored.
Todo
- cache data for getter functions to make it faster, just like what I did in SubX project
- When is
typeof path === 'symbol'? - Support React Hooks https://reactjs.org/docs/hooks-intro.html
- I think I mean function style react components
- Native objects 会报错,比如说
window.speechSynthesis.getVoices() autoRun逻辑上有漏洞。比如说我想保存一个对象。一开始这个对象的property不全。后来全了。但是新增的props并不被monitor。- 一个workaround是把property的值设为null。
- 不设为undefined,因为json不支持,持久化会有问题。 不过这个问题和本项目无关
- 一个workaround是把property的值设为null。
- 如果有循环引用的结构,会报错
Uncaught RangeError: Maximum call stack size exceeded
Notes
- every
emitter.on()must have a correspondingemitter.off(). Otherwise there will be memory leak.- you also don't have to
onandoffagain and again. Sometimes you justonand let it on until user explicit it request it to be off.- check the source code of
autoRun.
- check the source code of
- you also don't have to
- rewrite some emitter.on to promise.
- the idea is great, but it will turn the library from sync to async, which will cause unexpected consequences.
React.render,EventEmitter.on,rxjs.observable.nextare all sync, there must be a good reason to stay with sync.
3 years ago