Skip to content
/ vux Public

Vux experiment - is possible to have react app with vuejs-like API?

Notifications You must be signed in to change notification settings

pvasek/vux

Repository files navigation

Vux Experiment

I found there is already vue like implementation for react mobx.

Some ideas from this experiment were transfered to mobx-model.

Is it possible to have a simple API for our react applications similar to Vue.js with the following characteristics?

  • Component composability
  • Simplicity from user perspective
  • Single application state
  • Unidirectional flow
  • Functional-like actions (similar to Redux)
  • Type friendly

Questions

  • Have you seen similar API for react somewhere else?
  • What's wrong/bad on this approach (the implementation could be definitely better)?
  • Is it possible to integrated it with other frameworks? Which one could be a good fit?

Is this a stupid idea? Does it make sence to work on it? Please let me know

It is just about a model object

Everything is built around a model object. The model is defined by model template where the initial state, actions, models can be defined.

Run this example in JS Bin

const createCounterModel = () => new Model({
    initialState: {
        value: 0
    },
    actions: {
        increment: (state) => ({ value: state.value + 1 }), 
        decrement: (state) => ({ value: state.value - 1 }),
        set: (state, value) => ({ value }), 
        reset: (state) => ({ value: 0 })
    }
});

Model Template includes:

  • initialState: initial state as plain js object
  • actions: reducers
  • models: linked sub-models

Model includes:

  • state: immutable.js structure
  • $state: proxy which makes accessible state as js object
  • signals: action triggers
  • models: linked sub-models

The mapping from template to model could be described as:

  • initialState + models => state
  • actions => signals
  • models => models

You can subscribe to model for receiving changes and call the actions through the signals. Before the model object can be used it needs to be initialized. That creates initial state which is built from template initial state and sub-models.

Because internally the state is hold in immutable structure if we want to have states as js objects in our actions we need to call useJsStateForActions method.

defaultSettings.transformations.useJsStateForActions();

const model = createCounterModel();
model.subscribe(() => {
    console.log(model.state.toJS());
});
model.initialize();

model.signals.increment();
model.signals.increment();
model.signals.decrement();
model.signals.reset();
model.signals.set(10);

console.log(model.$state.value);

Model tree

The models can be composed to the model tree. Then you need to subscribe only to the root model.

Let's create counter pair where we will reuse our counter models.

Run this example in JS Bin

const createCounterPairModel = () => {
    const model = new Model({
        models: {
            left: createCounterModel(),
            right: createCounterModel()
        }
    });
    model.reset = () => {
        model.models.left.signals.reset();
        model.models.right.signals.reset();
    };
    return model;
};

You can attach custom methods to your model which calls submodels signals. This could be probably handled differently for example by defining custom signals in the template.

This can be used in the similar fashion as the simple model.

const model = createCounterPairModel();
model.subscribe(() => {
    console.log(model.state.toJS());
});
model.initialize();

model.models.left.signals.increment();
model.models.left.signals.increment();
model.models.right.signals.increment();
model.signals.reset();

console.log(model.$state.left.value);
console.log(model.$state.right.value);

Using it with React

If you want to use it with react you just need to pass it through props to your react component.

Run this example in JS Bin

class Counter extends React.Component {
    render() {  
        const model = this.props.model;
        return (
            <div>
                <span>{model.$state.value}</span>
                <button onClick={model.signals.increment}>+</button>
                <button onClick={model.signals.decrement}>-</button>
                <button onClick={model.signals.reset}>reset</button>
            </div>
        );        
    }
};

The initialization in that case can be done as following:

const appElement = document.getElementById('app');
const model = createCounterModel();
model.subscribe(() => {
    ReactDOM.render(React.createElement(Counter, {model}), appElement)
});
model.initialize();

The composition in this case is easy because you pass the root model to your root component and root component can passit down to its children.

Run this example in JS Bin

class CounterPair extends React.Component {
    render() {  
        return (
            <div>
                <Counter model={model.models.left}/>
                <Counter model={model.models.right}/>
                <button onClick={model.reset}>Reset</button>
            </div>
        );        
    }
};

The initialization then need to be updated as well:

const appElement = document.getElementById('app');
const model = createCounterPairModel();
model.subscribe(() => {
    ReactDOM.render(React.createElement(CounterPair, {model}), appElement)
});
model.initialize();

Dynamic sub-models

Right now the model has updateModels(models) method. This updates models, state and state proxy object. But because the API doens't suppor arrays in models definition it need to be hacked little bit more that it should be.

Problems

  • Because the model is passed down to react components and model instance doesn't change over time if we want to implement shouldComponentUpdate() we need to pass the model.state as well.
  • If you have custom method attached to the model which calls submodel actions. This generates multiple state updates.

Theoretical directions

Redux way: Right now the signals are just proxied to the methods. It could be possible to dispatch them up to the tree to the redux store and create reducer which would pass them down again. That could enable integration with redux.

Cycle.js way: Could we chagne signals to stream and then use cycle.js drivers?

About

Vux experiment - is possible to have react app with vuejs-like API?

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published