@tylerlong/use-proxy v1.3.1
deprecated
This project has been renamed to manate.
useProxy
The name useProxy was inspired by React useState.
Just like useState, it is mainly designed to work with React applications.
useProxy is the successor of SubX, which is similar to MobX.
What's the value of useProxy?
It allows you to maintain your app state in OOP style.
I am not saying that OOP style is the best practice for React development.
But if do want to code your React app in OOP style, you should give this library a try.
It supports TypeScript very well.
Demo application
Installation
yarn add @tylerlong/use-proxyUsage
import { useProxy } from '@tylerlong/use-proxy';
import { Component } from '@tylerlong/use-proxy/lib/react';
class Store {
count = 0;
increase() {
this.count += 1;
}
}
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>
);
}
}Functional React Component & React Hooks
import { auto } from '@tylerlong/use-proxy/lib/react';
const App = (props: { store: Store }) => {
const { store } = props;
const render = () => (
<Space>
<Button onClick={() => store.decrease()}>-</Button>
{store.count}
<Button onClick={() => store.increase()}>+</Button>
</Space>
);
return auto(render, props);
};It's fully compatible with useState and useEffect.
A fully working demo is here.
Event Emitter
import { useProxy } from '@tylerlong/use-proxy';
import { ProxyEvent } from '@tylerlong/use-proxy/lib/models';
class Store {}
const store = useProxy(new Store());store.$e is an EventEmitter which will emit events about read/write to store. You can subscribe to events:
store.$e.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.$e 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.
Question #1: why not use autoRun to support React hooks?
Well, actually it is possible and implementation is even shorter and simpler:
const auto = (render, props): JSX.Element | null => {
const [r, refresh] = useState(null);
useEffect(() => {
const proxy = useProxy(props);
const { start, stop } = autoRun(proxy, () => {
refresh(render());
});
start();
return () => {
stop();
releaseChildren(proxy);
};
}, []);
return r;
};Big problem is:https://github.com/tylerlong/use-proxy-react-demo/blob/03ca533592a78a446d3688274c7b47059644dda3/src/index.tsx。
Upstream components cannot invoke render, because render is inside useEffect. So upstream useState becomes useless。
Another minor issue:
But there is an issue: React StrictMode doesn't works for us any more.
Because StrictMode will try to do double rendering. However, we only invoke render in useEffect.
So double rendering will not invoke render at all, thus it cannot help us to detect non-pure function issues.
So is there a way to run autoRun out of useEffect? Nope, because autoRun by design is long running process and has side effects.
It's not a good idea to run autoRun for every render. run is more suitable for this case.
Question #2: why use run to support React hooks?
According to the analysis above, if we want to support upstream component's useState and strictMode, we must run render outside useEffect.
However, run requires a proxy object. Building such a proxy object has side effects. And when to dispose side effects? If we cannot answer this question, we cannot use run.
After investigation, I found that useRef can be used to dispose the side effects created in last render.
Todo
- Rename to "manate": manage + state
- allow to
import {auto} from 'manate/react'instead ofimport {auto} from '@tylerlong/use-proxy/lib/react'- pretty hard
Development 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.
- you also don't have to
runandautoRunonly support sync methods. for async methods, make sure that the async part is irrelevant because it won't be monitored.- 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.
Known limitations
- It only monitors
getandsetof properties. It doesn't monitordelete,hasandkeys.- Because in 99.9% cases,
get&setare sufficient to monitor and manage data.
- Because in 99.9% cases,
- It doesn't work with some built-in objects, such as
Set&Map. - It desn't work with native objects, such as
window.speechSynthesis.getVoices(). autoRundoesn't monitor brand new properties. It only monitors existing properties.- workaround: pre-define all properties in the object. Event it doesn't have value yet, set it to
null.nullis better thanundefinedbecauseundefinedis not a valid value for JSON string.
- workaround: pre-define all properties in the object. Event it doesn't have value yet, set it to
- no circular references, otherwise
Uncaught RangeError: Maximum call stack size exceeded
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago