基於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。