一個簡潔, 強大的數據流框架; Githubhtml
npm install --save nearly-react
上圖爲 flux 架構圖, Nearly 參考自 flux, 在其基礎上作了如下簡化和改進:react
集成 Promise
, 咱們再也不須要多寫一個 componentDidMount
方法去異步獲取數據, 更多狀況下, 咱們將使用 stateless component
讓代碼更加簡潔;git
Store
的使用更加靈活, Store
的單實例和多實例使用能很巧妙地實現跨組件通訊和通用組件控制邏輯的複用;github
API 更加簡潔, 在業務中通常只會用到 connect
和 dispatch
方法;npm
對狀態進行集中管理, 寫法與原始的 React
類似, 學習和遷移成本低;json
更輕量, min 後只有 6K;架構
import React from 'react'; import { render } from 'react-dom'; import {connect, dispatch, registerStore} from 'nearly-react'; registerStore('counter', { // 必須實現 init 方法, 它將被隱式調用, 做用是初始化 state init() { return { count: 0 }; }, add(getState, step) { return { count: getState().count + step }; } }; let incr = () => dispatch('counter::add', 1); let decr = () => dispatch('counter::add', -1); function Counter(props) { return ( <div> <button onClick={incr}> - </button> <span>{props.count}</span> <button onClick={decr}> + </button> </div> ) } let HocCounter = connect('counter', Counter); render( <HocCounter />, document.getElementById('root') )
該方法將註冊一個 Store
, 須要注意的是該方法必須先 connect
執行, 例:mvc
registerStore('customStore', { // 必須實現 init 方法 init() { return {sum: 0}; }, add(getState, num) { return {sum: getState().sum + num}; } });
registerStore
接受的第二個參數裏的方法即 Dispatcher functions
;Dispatcher function
的第一個參數爲 getState
方法, 該方法返回的永遠是當前最新的 state
, 其他參數爲 dispatch
方法所傳的參數;框架
對於 Dispatcher function
的返回值:less
爲普通對象時, 返回值直接 merge 進舊 state;
爲 Promise
時, 取 Promise.prototype.then
方法裏的參數 merge 進舊 state;
爲 null
時, 不 merge, 不觸發 render;
例:
registerStore('counter', { // 必須實現 init 方法, init 中也可使用 Promise init() { return fetch('./test.json').then(res => res.json()); }, add(getState, step) { return { count: getState().count + step }; }, // 異步增長 addAsync(getState, step) { return new Promise(resolve => { setTimeout(() => { // getState 方法返回的永遠是最新的 state let count = getState().count + step; resolve({count}) }, 1000); }); }, // 不觸發渲染 nothing(getState, step) { return null; } };
默認配置的 action
格式爲 ${storeName}::${function}
,
dispatch 會根據 action
映射到相應的 Dispatcher function
, 並將 args 做爲參數傳入 Dispatcher function
, 將其返回的結果提交給 Store
, 由 Store
觸發組件更新;
該方法會根據 storeName
得到 Store
, 再將 Store
, Component
和 PlaceHolder
組合, 返回一個高階組件;
其中, PlaceHolder
爲默認展現組件 (可選), 當且僅當 init
返回 Promise
時有效, 在 Component
被插入 dom 以前, 組合後的高階組件會先展現 PlaceHolder
組件, 可用於實現 loading 之類的效果;
但組件過大時, 能夠經過設置 isPure
爲 true 來提升性能, 當設置 isPure
爲 true 時, 只有 dispatch
方法能觸發組件的 render
, 我相信這比經過在 shouldComponentUpdate
裏寫 shallowEqual
要有效得多;
也能夠經過下面的 configure
設置默認的 isPure
爲 true;
即 dispatch
的高階函數; 例:
dispatch('counter::add', 1); 等同於: dispatcher('counter::add')(1); dispatch('test::testAdd', 1, 2, 3, 4); 等同於: dispatcher('test::testAdd', 1, 2)(3, 4);
使用 nearly
進行開發, 咱們須要考慮 storeName
重複的狀況, 我推薦經過將 storeName
映射文件路徑的方式來避免;
nearly
提供了兩個可供配置的方法: beforeConnect
和 beforeDispatch
;
beforeConnect
會在 connect
方法被調用以前調用, 接受的參數爲傳入 connect
方法的 storeName
; 咱們能夠用它去加載對應的 JS 文件, 並註冊 Store
;
beforeDispatch
會在 dispatch
方法被調用以前調用, 接受的參數爲傳入 dispatch
方法的 action
;
默認配置以下:
import {registerStore, getStore} from './store'; let config = { // 默認的 isPure defaultPure: false, // 默認不開啓自動註冊 Store beforeConnect(storeName) { // let store = getStore(storeName); // if (!store) { // let realName = storeName.split('#')[0]; // registerStore(storeName, require(`./actions/${realName}.js`)); // } }, beforeDispatch(action) { let [storeName, dispatcherName] = action.split('::'); let store = getStore(storeName); if (!store) { throw Error(`store '${storeName}' does not exist`); } let dispatcher = store.dispatchers[dispatcherName]; if (!dispatcher) { throw Error(`the module does not export function ${dispatcherName}`); } return {store, dispatcher}; } }
使用示例:
import {configure, getStore, registerStore} from 'nearly-react'; configure({ beforeConnect(storeName) { // 配置 beforeConnect 方法, 自動註冊 Store // 當 store 不存在時 // 自動去 actions 目錄下加載 JS 模塊, 並註冊 Store let store = getStore(storeName); if (!store) { let realName = storeName.split('#')[0]; registerStore(storeName, require(`./actions/${realName}.js`)); } } });
在業務中咱們常常須要跨組件通訊, 或者組件間共享數據;
使用 Nearly 咱們能很輕易地將兩個不一樣的組件綁定相同的 Store
, 只要傳入 connect
的 storeName
是相同的便可;
例: 簡單的輸入同步顯示
registerStore('vm', { // 必須實現 init 方法, 它將被隱式調用, 做用是初始化 state init() { return { value: '' }; }, change(getState, value) { return { return { value }; }; } }; // /components/Input.js let change = e => dispatch('vm::change', e.target.value); function Input(props) { return <input value={props.value} onChange={change} /> } export default connect(Input, 'vm'); // /components/Text.js function Text(props) { return <p>{props.value}</p> } export default connect(Text, 'vm');
詳見示例: One-store
咱們開發通用組件時會須要給同一組件綁定同一 store
的不一樣實例以複用; 能夠經過給 storeName
加上 #id
來區分不一樣 Store
;
// Dialog.js export default function Dialog (props){ return <div>{props.content}</div> } let DialogA = connect(Dialog, 'dialog#a'); let DialogB = connect(Dialog, 'dialog#b'); // 關閉彈窗 A dispatch('dialog#a::close'); // 關閉彈窗 B dispatch('dialog#b::close');
注意, 當在組件內部使用 dispatch
時, 能夠經過 props._storeName
來肯定 storeName
;
詳見示例: Dialog
nearly-config.js
必須在業務邏輯以前加載;
雖然有 registerStore
API, 不過做者仍是推薦使用 connect
來隱式註冊 Store
, 由於 connect
經過 storeName
映射文件的方式來註冊 Store
, 在確保惟一性的同時更容易維護和 debug;
在 Nearly 中對 Promise
的判斷是不許確的 (只要有 then
方法均認爲是 Promise
實例) , 一方面是由於 Nearly 中只使用了 then
方法, 另外一方面是爲了兼容 jQuery.Deferred
等類庫;
歡迎提 issue 或是 pr;