1. Resso, React state management has never been easier
resso is a brand new state manager for React that aims to provide the world's easiest way to use it.
At the same time, resso also implements on-demand updates. If the unused data of components changes, it will never trigger component updates.
GitHub: https://github.com/nanxiaobei/resso
import resso from 'resso';
const store = resso({ count: 0, text: 'hello' });
function App() {
const { count } = store; // 先解构,再使用
return (
<>
{count}
<button onClick={() => store.count++}>+</button>
</>
);
}
There is only one API resso
, just wrap the store object and nothing else.
If you need to update, you can reassign the key of the store.
2. How does the React state manager work?
Suppose there is a store, injected into a different component:
let store = {
count: 0,
text: 'hello',
};
// Component A
const { count } = store;
const [, setA] = useState();
// Component B
const { text } = store;
const [, setB] = useState();
// Component C
const { text } = store;
const [, setC] = useState();
// 初始化
const listeners = [setA, setB, setC];
// 更新
store = { ...store, count: 1 };
listeners.forEach((setState) => setState(store));
Put the setState of each component into an array, and when updating the store, call listeners
once, so that the update of all components can be triggered.
How to monitor store data changes? You can provide a public update function (eg Redux's dispatch
), which is an update if called. You can also use proxy's setter to monitor.
Yes, almost all state managers work this way, it's that simple. For example, the source code of Redux: https://github.com/reduxjs/redux/blob/master/src/createStore.ts#L265-L268
3. How to optimize the update performance?
Every time the store is updated, all setState in listeners
is called, which can cause performance problems.
For example, when updating count
, in theory, only A is expected to be updated, and at this time, B and C are also updated, but they do not use count
at all.
How to update on demand? You can use the selector method (such as Redux's useSelector
, or the implementation of zustand):
// Component A
const { count } = store;
const [, rawSetA] = useState();
const selector = (store) => store.count;
const setA = (newStore) => {
if (count !== selector(newStore)) {
rawSetA(newStore);
}
};
In the same way for other components, you can subscribe the new setA
to listeners
to realize the "on-demand update" of the components.
The above functions can also be implemented by using the proxy's getter, and the data "used" by the component can be known through the getter.
4. How is resso implemented internally?
In the above implementation, a setState is collected in each component. When updating the store, determine whether to update the component through data comparison.
Resso uses a new idea, which is actually more in line with the metadata concept of Hooks:
let store = {
count: 0,
text: 'hello',
};
// Component A
const [count, setACount] = useState(store.count);
// Component B
const [text, setBText] = useState(store.text);
// Component C
const [text, setCText] = useState(store.text);
// 初始化
const listenerMap = {
count: [setACount],
text: [setBText, setCText],
};
// 更新
store = { ...store, count: 1 };
listenerMap.count.forEach((setCount) => setCount(store.count));
Use useState
inject each store data used in the component, while maintaining a list of updates for each key in the store.
The number of setStates collected in each component corresponds to the store data used. Instead of just collecting a setState for component updates.
When updating, there is no need to do data comparison, because the update unit is based on the "data" level, not based on the "component" level.
To update a certain data is to call the update list of this data, not the update list of the component. Metadata the entire store.
5. How is the API of resso designed?
The secret to designing an API is to write the usage you want first, and then think about how to implement it. What comes out of this must be the most intuitive.
Resso also thought about the following API designs at the beginning:
1. Similar to valtio
const store = resso({ count: 0, text: 'hello' });
const snap = useStore(store);
const { count, text } = snap; // get
store.count++; // set
This is standard Hooks usage, with the disadvantage of adding an extra API useStore
. And use snap when get, use store when set, people split, this is definitely not the "simplest" design.
2. Similar to valtio/macro
const store = resso({ count: 0, text: 'hello' });
useStore(store);
const { count, text } = store; // get
store.count++; // set
This is also possible and is standard Hooks usage. At this time, the main body of get and set is unified, but it is still necessary to add a useStore
API. This thing is only for calling Hooks. What if the user forgets to write it?
And in practice, it is found that when using store in each component, you have to import two things, store and useStore, which is definitely not as simple as importing only one store, especially when it is used in many places, it will be very troublesome.
3. To import only one store
const store = resso({ count: 0, text: 'hello' });
store.useStore();
const { count, text } = store; // get
store.count++; // set
This is the last hope for a "legitimate" use of Hooks, just importing a store, but it still looks weird and unacceptable.
If you try to design this API, you will find that if you want to directly update the store (requires import store), and want to deconstruct store data through Hooks (you need to import one more Hook, and get and set are different sources at the same time), this design will work no matter what. It looks awkward.
For ultimate simplicity, for the easiest way to use, resso finally embarked on this API design:
const store = resso({ count: 0, text: 'hello' });
const { count } = store; // get
store.count++; // set
6. How to use resso
Get store
Because the store data is injected into the component with useState
, it needs to be deconstructed first (deconstruction means calling useState
), deconstructed at the top level of the component (ie Hooks rules, cannot be written after if
), and then used, otherwise there will be a React warning.
Set store
Assignment to the first layer of data in the store will trigger an update, and only the assignment of the first layer of data will trigger an update.
store.obj = { ...store.obj, num: 10 }; // ✅ 触发更新
store.obj.num = 10; // ❌ 不触发更新(请注意 valtio 支持这种写法)
resso does not support the writing method of valtio, mainly because of the following considerations:
- It is necessary to traverse all the data deeply for proxying, and when updating the data, it needs to be proxyed first, which will cause a certain performance loss. (resso only does proxy store once at initialization.)
- Because all data is proxy, it is not friendly to display in Chrome console, which is a big problem. (Resso will not have this problem, because only the store is a proxy, and generally prints the data in the store.)
- If the sub-data is deconstructed, such as
obj
andobj.num = 10
, the update can also be triggered, which will cause the data source to be opaque. It is uncertain whether it comes from the store and whether the assignment triggers the update. (The main body of resso updates is always the store, and the source is clear.)
7. Make simple, not chaos
The above is the design concept of resso, and some implementations of the React state manager.
At the end of the day, the React state manager is a tool, React is a tool, JS is a tool, programming is a tool, and the job itself is a tool.
The purpose of tools is to create, to create works that act on the real world, not the tools themselves.
So, why not make it simpler?
jQuery is to simplify the development of native JS, React is to simplify the development of jQuery, development is to simplify the process in the real world, the Internet is to simplify people's communication path, work path, consumption path, the meaning of development is to simplify, the meaning of the Internet It is simplification, and the value of the Internet lies in simplification.
So, why not make it simpler?
Chic. Not geek.
Simplicity is everything.
try try resso: https://github.com/nanxiaobei/resso
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。