Better data flow management framework: Vanex released~

vanex

The React store management framework based on mobx & mobx-react provides a simple and fast development paradigm. The mode of use is similar to dva, but it is simpler and more efficient than dva.

gitlab address: http://gitlab.alibaba-inc.com...
example address: http://gitlab.alibaba-inc.com...

Characteristic

Three API s to fix the problem! Simple and easy to use, high efficiency of development.

How to use

Vaex provides a one-click initialization start method, and the entry file can start as follows:

import React from 'react';
import App from './App';

import {
    start,
} from 'vanex';

// model
import user from './models/User';
import todos from './models/Todos';

start({
    component: App,
    container: '#root',
    models: {
        user,
        todos
    }
});

So, just pass in your model (like tarot module), React Container Component, Middleware (optional), Relation, and the application will run.

The following concepts are introduced:

  • model: Data management, different from tarot, has only two core parts: name space and data, action. Action can store operations similar to Reducers and Effects at the same time.

  • Middleware: Middleware for assisting asynchronous processing. The process that an action of model redefinition is finally executed is as follows: first, it will be packaged by the action function of mobx to avoid that every change of data will trigger a re-rendering of UI, then it will be executed by each Middleware in turn, and each middleware has three operations before/after/error, which can handle each operation uniformly in different operations;

  • relation: Used for communication between different model s, based on listening subscription mode.

container Component based on vanex development paradigm is also UI Component, UI Component is like the following:

import React, {Component, PropTypes} from 'react';

// components
import UserLogin from './components/UserLogin';
import UserDetail from './components/UserDetail';
import Todos from './components/Todos';

import {
    inject,
    observer,
} from 'vanex';

// Note that first observer, then inject
@inject('user')
@observer
export default class App extends Component {
    render() {
        // const user = this.props.user.toJSON();
        console.log(this.props.user.toJSON());
        const {user} = this.props;

        console.log('user.isLogin:', user.isLogin);

        if (user.isLogin !== true) {
            return <UserLogin />;
        }

        return (
            <div>
                <UserDetail />
                <Todos />
            </div>
        );
    }
}

Here oberser comes from the observer of mobx, while inject comes from mobx-react. If you want to inject multiple model s into a Component at the same time, you can do the following:

// start
import React from 'react';
import App from './App';

import {
    start,
} from 'vanex';

// model
import user from './models/User';
import todos from './models/Todos';

start({
    component: App,
    container: '#root',
    models: {
        user,
        todos
    }
});
import {
    inject,
    observer,
} from 'vanex';

@inject(
    stores => ({
        user: stores.user,
        todos: stores.todos,
    })
)
@oberser
class MyComponent extends Component{
    constructor(props, context) {
        super(props, context);
    }

    render() {
        const {
            user,
            todos,
        } = this.props;

        return (
            <div>{user.name}</div>
        );
    }
}

The observer API of mobx is used to turn React Component into observable (dynamic collection dependency). After manipulating some data in the model, if the modified data is just used by the React component, it will trigger the component's re-rendering, which is why mobx can fine-grained control the data.

The inject API of mobx-react is used to specify which models are injected into React Component(this.props.modelName), and also to specify which data the component is Observeable based on.

model

The code is similar to the following:

import TodoItem from './TodoItem';
import * as api from '../api';

export default {
    name: 'Todos',
    data: {
        list: [],
    },
    syncs: {
        add(text, userId) {
            // Similar to Vue, manipulation of arrays triggers a re-rendering of the UI
            this.list.push(new TodoItem({
                text,
                userId
            }));
        },
    },
    effects: {
        async getByUserId(userId) {
            let todos = await api.getTodosByUserId(userId);
            todos = todos.map(todo => new TodoItem(todo));
            // Similar to Vue, manipulation of arrays triggers a re-rendering of the UI
            this.list = this.list.concat(todos);
        },
    }
};

The model consists of the following parts:

  • 1. name: Namespace of the current model;

  • 2. constants: Invariable constants;

  • 3. Data: Operational data part;

  • 4. syncs: Synchronized operation data part;

  • 5. effects: asynchronous processing part;

  • 6. init: Callback method after initializing model;

  • 7. autorun: A method that automatically executes data after each operation.

Trigger action

model internal trigger

Data data defined within the model is assigned to the model instance, so any data defined in the data can be referenced by this. xxxx, as follows:


import fetch from 'whatwg-fetch';

const pageSize = 20;

export default {
    name: 'Applications',

    data: {
        dataSource: [

        ], // Listed data

        detailPageVisible: false,

        campaignDetail: {},
    },

    syncs: {
        validate(value) {
            value = value ||'';

            // xxxx

            return {
                code: 200
            };
        },
    },

    effects:{
        async getList(payload = {}) {
            const {
                currentPage = 1,
            } = payload;

            const url = `/applications/list/${currentPage}?pageSize=${pageSize}`;

            let res = await fetch(url);

            res = res.body;

            const validateRes = this.validate(res);

            if(validateRes.code == 200) {
              this.dataSource = res.data; // This triggers a re-rendering of the corresponding Component
              this.currentPage = res.currentPage;
              this.totalItem = res.totalItem;
              this.totalPage = res.totalPage;
            }

            return res;
        },
    }
};

As you can see, changing the data is a direct assignment to the model instance, which is simple, direct and efficient, and multiple assignments only trigger a re-rendering once. You can imagine that if a page is a list list, the user needs to modify the data and display of one item in the list after operating on it, and only need to perform something similar to:

this.props.home.list[2].name = 'New Name';

Can the code complete name data processing and page display changes? Think about it and get excited.

Some students will have: syncs and effects, many times in the model direct assignment will trigger the UI's multiple rendering concerns, in fact, will not, our team syncs and effects inside each method using the use of mobx action to do a layer of packaging, so as to avoid this problem.

In addition, we also provide an auxiliary method of this.set() to easily change the value of the model, so you can also do this:

this.set({
  dataSource: res.data,
  currentPage: res.currentPage,
  totalItem: res.totalItem,
  totalPage: res.totalPage,
});

The runInAction of mobx is used here to perform uniformly to ensure that UI rendering is performed only once.

Intra-component triggering

As follows, simple and direct:

import { inject, observer } from 'vanex';

@inject('applications')
@observer
class Applications extends Component {
    constructor(props, context) {
        super(props, context);
    }

    clickHandler() {
      this.props.applications.getList(); // Direct execution
    }

    render() {
      return (
        <div onClick={::this.clickHandler}></div>
      );
    }
}

Vanex plug-in mechanism

Vanex supports plug-in mechanisms in the following ways:

import { start, use } from 'vanex';

import effectPlugin from './effect-plugin';

use(effectPlugin);

// start code

The list of plug-ins that have been provided is as follows:

onStateChange

The trigger callback is used to monitor data changes. The format is as follows:

export default {
    onStateChange: [event => {
        console.log(event);
    }]
};

onEffect

Used to deal with asynchronous execution before, after, error, and filter which effects execute the callback, it is actually executed in the form of middleware when executed. If you have a requirement similar to csrfToken that comes with each request, you can assemble it in the beforehook function.

Specific uses are as follows:

// Before exec action
function preLogger({
    type,
    payload
}) {
    console.log(`[${type}] params: `, payload);

    payload. csrfToken = 'xxx'; // The changes here will take effect on the request parameters

    return payload;
}

// Action exec fail
function errorLogger({
    type,
    payload
}) {
    console.log(`[${type}] error: `, payload.message);
    return payload;
}

// After exec action
function afterLogger({
    type,
    payload
}) {
    console.log(`[${type}] result: `, payload);
    return payload;
}

export default {
    filter({
            type
        }) {
        return /^User/.test(type); // Execution of the following hook function only for Model name User
    },
    before: preLogger,
    after: afterLogger,
    error: errorLogger,
};

onAction

Used to trigger after executing syncs Action. The format is as follows:

export default {
    onAction: [(
        actionName,
        actionArgs,
        result) => {
            console.log(`Current implementation Action Name: ${actionName}`);
            console.log(`Current implementation Action Parameters: ${actionArgs}`);
            console.log(`Current implementation Action Results: ${result}`);
        }]
};

getActionState

This is not a Vanex plug-in, but is used to solve the problem of getting whether an effect in the current model is sending a request in the component, and this state can be used to conveniently control whether the Loading component is visible. Because this requirement is very common, Vanex is built directly into the internal implementation. The following examples are used:

const {
    user
} = this.props;

const {
    loading: loginLoading,
    error: loginError
} = user.getActionState('user/login');

Used to develop components

Sometimes, instead of performing page rendering, we use Vanex to develop a component. At this point, we can still use the start API, which returns a React Component as long as we don't pass the container value.

import React from 'react';
import { render } from 'react-dom';
import App from './App';

// load middlewares
import middlewares from './middlewares';

import {
    start,
    use,
} from 'vanex';

use({
    onEffect: middlewares
});

// model
import user from './models/User';
import todos from './models/Todos';

// relation
import relation from './relations';

// Verify that start returns a component
const MyComponent = start({
    component: App,
    models: {
        user,
        todos
    },
    relation
});

render(<MyComponent data={{a: 1}} />, document.querySelector('#root'));

Characteristic

  • Simple and easy to use, high efficiency of development;

  • MVVM: Vanex implements the development paradigm of MVVM based on React, which is simple, direct and efficient.

  • Change store data: direct assignment;

  • Trigger action: directly execute store action;

  • Performance optimization: Automatically do it.

Why is the development paradigm based on mobx simpler and more efficient?

Mobx's implementation idea is almost the same as Vue's, so its advantages are similar to Vue's: by monitoring the change of attributes of data (objects and arrays), it can trigger the rendering of UI by changing the data directly, thus achieving MVVM, responsiveness, low cost and high development efficiency. The difference in data management needs to be elaborated in detail.

Redux is the only global Store recommended. Multiple Reducers will be merged into a root reducer before they are passed to react-redux. Any change in data (through Reducer) will trigger the re-rendering of the entire UI tree through this store. Without any performance optimization (pureRender, etc.), even if VD(Virtual Dom) is more efficient, when the amount of page data is increased. The number of DOM is large, and the performance consumption is also very large. On the other hand, Redux implements data management by pull ing, that is, it can only wait for the application to distribute an Action, and then trigger the rendering of the UI, but can not predict the behavior; Mobx is different, it is based on monitoring data attribute changes, and it is multi-stores, for any data changes are the first time to know. So it is based on push's listening and subscribing mode, so that it can achieve predictable data and fine-grained control, and even reduce performance consumption by modifying the life cycle of React components without users'concern for these details. Of course, all of this can only be achieved by mobx observing components, so the more observe rs are used, the higher the application performance.

Thank

The partial implementation of Vanex refers to the MVVM framework: mobx-roof.

To ground

Keywords: Javascript React Vue GitLab Attribute

Added by iainr on Sun, 02 Jun 2019 20:59:16 +0300