The future with proxy
We programmers are dreamers . We are following the hype, dreaming of a new silver pool that will solve all our problems. And also, we love to write new bikes, thereby not solving problems, but creating new ones. In this article, let's dream a little about architecture, developing a “Pseudo-new” bicycle.
Let's try to develop our architectural solution using modern approaches, with an eye on the older brothers Redux , Mobx , etc.
Being in a wonderful time in which the previously unnecessary JS, capable of just animating DropDown, has become an unexpectedly powerful language! With a bunch of interesting, new features and rethought "old" features. More and more often, we don’t go head-on, but design our architecture, apply patters and solutions that can facilitate development and create scalable and easily digestible code.
So, let's begin!
I’ll make a reservation right away that in our bike we will solve the issue of not rendering View, but storing data and reactions to their changes.
The future has already come! We have ES2015 + ! With him we can work miracles! But wait ... What does he give us? To take this and write our new super-bike, which must certainly solve all our problems?
Let's look at the proxy ! Proxy is a way to track operations on an object, such as deletion, modification, and more. Now proxyIt is supported everywhere except IE11, Vue3 will use it under the hood. Well, and what’s worse, there’s nothing to produce legacy, let IE11 be left out of our galley, we will sail to a brighter future. In short, for cycling the first ingredient will be - Proxy .
But Proxy does not solve all problems, and it should not solve, just a way to track interactions with an object. Let's look at the best practices of our favorite solutions.
Redux , what do we love about it? A single place for storing data for our application is able to handle changes in one place without spreading the logic in all parts. This is a plus, take it! Middlewares, they are able to expand and complement the functionality, organize logging, proxying, etc. Wonderful! But the main plus of Redux 'a, as for me, is simplicity. So we, in our bike, need to do everything as simple as possible.
Of the minuses, this is work with asynchrony. A lot of extra code that you have to write ... We must somehow avoid this.
Mobx is a great solution to track changes. This we will do Proxy . The only thing in the future is to teach Proxy how to work with nested structures.
Typing . It would be nice to use this popular topic today and even supplement it. Let's make typing and validation in runtime in our super solution, taking into account nesting.
RxJS , I love him for the fact that in many cases he is able to solve complex problems for me, but we do not need to implement it. We’ll try not to complicate our decision, but to use ordinary JS objects and maybe Rx will work with our bike as needed.
React , here I would like to stay in more detail. React is a crucial framework. Before React'a we had Ember and Angular 1 giving a primary idea of the component approach. We wrote a lot of components and directives, complementing the basic logic that was concentrated in controllers, services, etc. But with the advent of React , we began to consider our application more declaratively, without running away from HTML, in the same context, and we can say the syntax.
The declarative nature of the component approach is what we need to achieve so that everything is as transparent and clear as possible. The trendy render-props now provide an excellent opportunity not to get out of the stream and write logic even more declaratively than HOCs, so let's take them.
After we’ve roughly figured out how it should all look, let's start writing code
Each of the modern frameworks is good in its own way. We are now solving the problem of storing the state; in fact, we should use it anywhere. In this article, for example, I chose React , because I know it better. But ideally, it should be universal.
Create the StateManager component. This is the main component that stores all the states of the application, like Redux . If desired, we can add validation (for example, this solution ) and typification (TypeScript / Flow). And also, write optional Middlewares functionality that will work out the changes before, after, during (placing their call in Proxy).
statemanager component
class StateManager extends Component {
componentWillMount() {
this.data = this.props.data || {};
this.proxify({props: this, val: 'data'});
}
proxify = ({props, val}) => {
Object.keys(props[val])
.filter(prop => typeof props[val][prop] === 'object' || Array.isArray(props[val][prop]))
.map(prop => ({props: props[val], val: prop}))
.forEach(item => this.proxify(item));
props[val] = this._makeProxy(props[val]);
};
_makeProxy = props => {
return new Proxy(props, {
set: (target, key, args) => {
if (typeof args === 'object' || Array.isArray(args)) {
target[key] = args;
this.proxify({props: target, val: key});
}
else {
target[key] = args;
}
setTimeout(this.forceUpdate.bind(this));
return true;
},
deleteProperty: (oTarget, key) => {
setTimeout(this.forceUpdate.bind(this));
return true;
}
});
};
render() {
return this.props.children(this.data);
}
}
StateManager.propTypes = {
data: PropTypes.object.isRequired
};
In order, what is happening here. From the outside, we are forwarding the data of our application, or an empty object (circuit), which we will fill in during the process. We wrap our application
Using StateManager
{store =>
...НАШЕ ПРИЛОЖЕНИЕ…
}
The proxify method recursively transforms into Proxy all objects / arrays, making them susceptible to changes, through the _makeProxy method .
In the proxy object itself, I added 2 hooks - set , for any change, deleteProperty - for any deletion. In set, we look, if we have an object, array, or deep data, we run them again through proxify , and then call forceUpdate , which will redraw all the children with the updated state of our application.
Throwing an altered state through render-prop, we can automatically scatter it in any part of the application.
Since, in fact, this is an ordinary object, we can screw the same Rx to it , or anything else, mutate it at any time, and generally pervert how much it fits.
In speed, the proxied object is significantly different from the usual assignment. Took measurements on creating and assigning a string to an object with a nesting depth of 2:
for a regular object
~ 0.006ms
Proxy
~ 0.05ms
This is a normal assignment without a reaction and other things. Let's hope this is optimized.
With this primitive approach, written in a few hours “on the knee”, you can create a to-do list in which data comes asynchronously, and we can control the output of elements
To-do list
{store =>
{
store.state.todo &&
store.state.todo.items &&
[
{
store.state.todo.items
.map((item, index) =>
-
{item.name}
{item.state === 'done' ?
:
}
)
}
,
this.input = c} />
,
]
}
State:
{JSON.stringify(store, true , 2)}
}
With this example, I would like to share with you my thoughts on using Proxy and some good practices. I hope that the use of new standards will inspire you to create complete solutions that will worthily take their place in our JS zoo. Thank you all, good luck in cycling!
Links:
→ Source Code
→ To-do list