基于mobx & mobx-react
的React store管理框架,提供简单快捷的开发范式。使用模式相似dva,但用起来比dva更简单,开发效率更高! react
github地址: https://github.com/alibaba/vanex
example地址: https://github.com/alibaba/va...git
三个API
搞定问题!简单易上手,开发效率高。github
vanex提供了一键初始化的
start
方法,入口文件能够像下面这样开始:redux
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 } });
因此,只须要把你的model(相似于tarot的module)、React Container Component、Middleware(可选)、Relation传递进来,应用就能跑起来了。api
介绍下几个概念:数组
model
: 数据管理,区别于tarot,其只有:name命名空间以及data、action两个核心部分,action部分能够同时存放相似于Reducers以及Effects两个部分的操做(做为优化,后续这里能够作拆分);middleware
:中间件,用于辅助异步处理。model重定义的一个action最终被执行的流程是这样的:首先其会被mobx的action函数包一层,以免掉每次更改数据都会触发一次UI的从新渲染,而后其会被各个中间件依次执行,而每一个中间件都有before/after/error三个操做,能够在不一样的操做中对每一种操做作统一的处理;relation
:用于不一样model之间的通讯,基于监听订阅模式。基于vanex的开发范式的
container Component
也是UI Component,UI Component像下面这样:性能优化
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'; // 注意先observer,后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> ); } }
这里的oberser
来自于mobx的observer,inject
则来自于mobx-react。若是想给一个Component同时注入多个model,则能够像下面这样:app
// 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> ); } }
mobx的observer API,用于将React Component变成observable的(动态收集依赖),在对model中的某些数据作了操做以后,若是被修改的数据恰好被该React组件使用到了,就会触发该组件的从新渲染,这也就是mobx能细粒度控制数据的缘由所在。 框架
mobx-react的inject API,用于指定将哪些model注入进React Component(this.props.modelName),也就指定了该组件基于哪些数据作Observeable。dom
代码相似于下面这样:
import TodoItem from './TodoItem'; import * as api from '../api'; export default { name: 'Todos', data: { list: [], }, syncs: { add(text, userId) { // 相似于Vue,对数组的操做会触发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)); // 相似于Vue,对数组的操做会触发UI的从新渲染 this.list = this.list.concat(todos); }, } };
model由如下几个部分组成:
model内部定义的Data数据,会被赋值到model实例上,因此任何在Data中定义的数据均可以经过this.xxx的方式来引用,以下:
import fetch from 'whatwg-fetch'; const pageSize = 20; export default { name: 'Applications', data: { dataSource: [ ], // 列表显示的数据 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; // 这样就会触发对应Component的从新渲染 this.currentPage = res.currentPage; this.totalItem = res.totalItem; this.totalPage = res.totalPage; } return res; }, } };
能够看到,更改数据则是直接给model实例赋值便可,简单直接高效,并且屡次赋值只会触发一次的从新渲染。你能想象若是一个页面是一个list列表,用户对列表中某一个进行操做后,须要修改这一项的数据及显示,只须要执行相似于:
this.props.home.list[2].name = 'New Name';
的代码就能完成name的数据处理及页面展现更改吗?想一想就激动是否是。
有的同窗会有:syncs和effects里面屡次对model直接赋值会触发UI的屡次渲染
的担忧,其实不会的,咱们队syncs以及effects里面的每个方法都用会使用mobx的action
作了一层包装,从而来避免这个问题。
另外,咱们也提供this.set()
的辅助方法来方便的为model改值,因此你还能够这样作:
this.set({ dataSource: res.data, currentPage: res.currentPage, totalItem: res.totalItem, totalPage: res.totalPage, });
这里会使用mobx的runInAction
来统一执行,从而保证UI渲染只执行一次。
以下,简单直接:
import { inject, observer } from 'vanex'; @inject('applications') @observer class Applications extends Component { constructor(props, context) { super(props, context); } clickHandler() { this.props.applications.getList(); // 直接执行 } render() { return ( <div onClick={::this.clickHandler}></div> ); } }
Vanex支持插件
机制,使用的方式以下:
import { start, use } from 'vanex'; import effectPlugin from './effect-plugin'; use(effectPlugin); // start代码
目前已经提供的插件列表以下:
用于监听数据发生改变的时候的触发回调。格式以下:
export default { onStateChange: [event => { console.log(event); }] };
用于处理异步执行
执行前(before)、后(after)、错误(error)
以及过滤哪些effects执行该回调,它在执行的时候实际上是以中间件的形式来执行的。若是有相似于每次请求都自带csrfToken
的需求,则能够在before
钩子函数中组装。
具体使用以下:
// Before exec action function preLogger({ type, payload }) { console.log(`[${type}] params: `, payload); payload。csrfToken = 'xxx'; // 这里的更改会对请求参数生效 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); // 只针对Model名字是User的进行下面钩子函数的执行 }, before: preLogger, after: afterLogger, error: errorLogger, };
用于在执行syncs
Action以后触发。格式以下:
export default { onAction: [( actionName, actionArgs, result) => { console.log(`当前执行Action的名字:${actionName}`); console.log(`当前执行Action的参数:${actionArgs}`); console.log(`当前执行Action的结果:${result}`); }] };
这个并非Vanex插件,可是用于解决在组件中获取当前model中某个effect是否正在发送请求
的问题,而这个状态能够用于方便的控制Loading组件是否可见
。由于这种需求很是广泛,因此Vanex直接内置到内部实现中。使用示例以下:
const { user } = this.props; const { loading: loginLoading, error: loginError } = user.getActionState('user/login');
有时候,咱们并不想执行页面渲染,而是用Vanex来开发一个组件,这时,仍是可使用start
API,只要不传如container
值,就会返回一个React Component。
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'; // 验证start返回一个组件 const MyComponent = start({ component: App, models: { user, todos }, relation }); render(<MyComponent data={{a: 1}} />, document.querySelector('#root'));
Mobx的实现思想和Vue几乎同样,因此其优势跟Vue也差很少:经过监听数据(对象、数组)的属性变化,能够经过直接在数据上更改就能触发UI的渲染,从而作到MVVM、响应式、上手成本低、开发效率高,在数据管理上须要再详细阐述下其区别。
Redux是建议全局惟一Store的,多个Reducers也会在传递给react-redux以前被合并成一个root reducer,任何数据的更改(经过Reducer)都会经过这一个store来触发整个UI树的从新渲染,若是不作任何的性能优化(pureRender等),就算VD(Virtual Dom)有了再高的效率提高,当页面数据量、DOM数量大了,性能消耗也是很是大的。另一点,Redux实现的对数据的管理是pull方式的,就是说其只能等待应用派发某个行为(Action),而后从新触发UI的渲染,而作不到对行为的可预期;Mobx则不同,他是基于监听数据的属性变化来实现的,并且是多store的,对于任何的数据变动都是第一时间知道的,因此其实现方式是基于push的监听订阅模式而实现,这样,他就能够作到对数据的可预测以及细粒度的控制,甚至能够经过修改React组件生命周期的方式来减小性能的消耗,而无需使用者对这些细节关心。固然这一切确定是有了mobx对组件作observe操做才能实现的,因此也就有了observer用的越多,应用性能越高的说法。
Vanex的部分实现参考自MVVM框架:mobx-roof。