The idea of writing the article appeared in this thread , maybe someone will be interested and read it. I must say, the writer (including the code) of me is so-so, but I will try.
We will write as usual tudulist, tired of course to hell, but it’s hard to think of something better to demonstrate. Immediately link to the running application: zhmyak ( code ).
And immediately into battle, let's start with the store. The only type needed for this application is Todo:
import { EventEmitter } from 'cellx'; import { observable } from 'cellx-decorators'; export default class Todo extends EventEmitter { @observable text = void 0; @observable done = void 0; constructor(text, done = false) { super(); this.text = text; this.done = done; } }
It's all very simple, a couple of observable fields, one contains the text of the task, the other the status of its implementation.
Inheritance from cellx.EventEmitter
necessary in case you later need to subscribe to changes of some field:
todo.on('change:text', () => {/* ... */});
In this application there is no such thing and inheritance can be removed, I just for some reason always write it in advance.
Now we will write the root repository:
import { EventEmitter, cellx } from 'cellx'; import { observable, computed } from 'cellx-decorators'; import Todo from './types/Todo'; class Store extends EventEmitter { @observable todos = cellx.list([ new Todo('Primum', true), new Todo('Secundo'), new Todo('Tertium') ]); @computed doneTodos = function() { return this.todos.filter(todo => todo.done); }; } export default new Store();
Here is more interesting. Cellx.list is used (alias for new cellx.ObservableList
) is an observable list, inherits from cellx.EventEmitter
and, for any change, generates a change
event. Observed field receiving as a value something inheriting from cellx.EventEmitter
subscribes to its change
and also changes during this event. All this means that it is not necessary to use the built-in collections, you can make your inherit them from cellx.EventEmitter
. Out of the box are cellx.list
and cellx.map . A separate module is the indexed versions of both collections: cellx-indexed-collections .
Another newcomer is the computed decorator, the calculated fields are the very essence of cellx-a - you just write the formula of the calculated field, you don’t need to subscribe to each todo done
when you add it and unsubscribe from it when you delete it, cellx does it all you do not see, you need to relax and have fun describing the essence. In this case, the description takes place, one can say, in a declarative form - you no longer need to think about events and how the changes will be distributed throughout the system, everything is written as if it will work only once. In addition, cellx is very smart and automatically makes some tricky optimizations: dynamic updating of dependencies and the collapse and dropping of events will not allow redundant calculations and interface updates. If you do all this manually, the code is quite voluminous, but, much worse, it is buggy. But debugging a cellx takes place once a hundred years, it just works.
Go to the display layer. First, the task component:
import { observer } from 'cellx-react'; import React from 'react'; import toggleTodo from '../../actions/toggleTodo'; import removeTodo from '../../actions/removeTodo'; @observer export default class TodoView extends React.Component { render() { let todo = this.props.todo; return (<li> <input type="checkbox" checked={ todo.done } onChange={ this.onCbDoneChange.bind(this) } /> <span>{ todo.text }</span> <button onClick={ this.onBtnRemoveClick.bind(this) }>remove</button> </li>); } onCbDoneChange() { toggleTodo(this.props.todo); } onBtnRemoveClick() { removeTodo(this.props.todo); } }
Here from the new one - the observer
decorator from the cellx-react module . Roughly speaking, it simply makes the render
method a computed cell and calls React.Component # forceUpdate when it changes.
The root component of the application remains:
import { computed } from 'cellx-decorators'; import { observer } from 'cellx-react'; import React from 'react'; import store from '../../store'; import addTodo from '../../actions/addTodo'; import TodoView from '../TodoView'; @observer export default class TodoApp extends React.Component { @computed nextNumber = function() { return store.todos.length + 1; }; @computed leftCount = function() { return store.todos.length - store.doneTodos.length; }; render() { return (<div> <form onSubmit={ this.onNewTodoFormSubmit.bind(this) }> <input ref={ input => this.newTodoInput = input } /> <button type="submit">Add #{ this.nextNumber }</button> </form> <div> All: { store.todos.length }, Done: { store.doneTodos.length }, Left: { this.leftCount } </div> <ul>{ store.todos.map(todo => <TodoView key={ todo.text } todo={ todo } />) }</ul> </div>); } onNewTodoFormSubmit(evt) { evt.preventDefault(); let newTodoInput = this.newTodoInput; addTodo(newTodoInput.value); newTodoInput.value = ''; newTodoInput.focus(); } }
Here are a couple of calculated fields, they differ from Store#doneTodos
only in that the fields from which they are calculated do not lie on the current instance ( this
), but somewhere else, cellx does not limit in this regard, these fields can be safely Move to the Store
and it will still work. It is better to determine where the field should lie in its essence - if the field is specific for a particular component, then even if it is calculated in it, it does not make sense to glow in the general storage. In this case, I would #leftCount
transfer to the repository, it may well be useful somewhere else, and #nextNumber
pretty good here too.
In cellx actions, it is not used at all, so I simplified them as much as possible, it turned out not even Flux, but some kind of MVC in terms of Flux. I hope you will forgive me for this simplification.
In this case, the application is quite simple and it is just as easy to write it without a cellx (no subscriptions to each done
here will be required), with further complication of the links in the application, the complexity of describing them on cellx-e grows linearly, without it - usually no, and at some point we arrive at a jumble of events in which one cannot understand without half a liter. To solve the problem, besides reactive programming, there are other approaches with their pros and cons, but comparing them is another story (if briefly, at least they lose because of the large number of unnecessary calculations and, as a result, lower performance).
In general, the code is all, once again a link to the result: zhmyak ( code ).
The most commonly asked differences from MobX . This is the closest analogue and the differences are few:
Here the differences are more significant. The lag in speed is even greater, but this is not the most important. These libraries propose to create calculated cells in a slightly different way, in a more functional form, probably. What cellx-e will look like:
var val2 = cellx(() => val() + 1);
On these libraries, turn into something like (pseudocode, I don’t remember exactly how, and it doesn’t matter):
var val2 = val.lift(add(1));
Plus in a more beautiful, human-readable code, a minus in a noticeably larger entry threshold, since now you need to memorize 100,500 methods for all occasions (you can of course get along with some sort of minimal set).
At the same time, cellx has the opportunity to add its own methods to cells and nothing prevents to bring it to the level of these libraries, it can be said that it is more low-level.
Questions about the library and ideas for its further development are accepted on the githaba .
Thank you for attention.
Source: https://habr.com/ru/post/313038/
All Articles