更簡潔易用的 react 數據流 nearly-react

Nearly

一個簡潔, 強大的數據流框架; Githubhtml

安裝

npm install --save nearly-react

特性

data-flow

上圖爲 flux 架構圖, Nearly 參考自 flux, 在其基礎上作了如下簡化和改進:react

功能上:

  • 集成 Promise, 咱們再也不須要多寫一個 componentDidMount 方法去異步獲取數據, 更多狀況下, 咱們將使用 stateless component 讓代碼更加簡潔;git

  • Store 的使用更加靈活, Store 的單實例和多實例使用能很巧妙地實現跨組件通訊和通用組件控制邏輯的複用;github

相比 flux:

  • API 更加簡潔, 在業務中通常只會用到 connectdispatch 方法;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')
)

API

registerStore(storeName, dispatcherSet)

該方法將註冊一個 Store, 須要注意的是該方法必須先 connect 執行, 例:mvc

registerStore('customStore', {
    // 必須實現 init 方法
    init() {
        return {sum: 0};
    },
    add(getState, num) {
        return {sum: getState().sum + num};
    }
});

Dispatcher functions(getState, ...args)

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;
    }
};

dispatch(action, ...args)

默認配置的 action 格式爲 ${storeName}::${function},

dispatch 會根據 action 映射到相應的 Dispatcher function, 並將 args 做爲參數傳入 Dispatcher function, 將其返回的結果提交給 Store, 由 Store 觸發組件更新;

connect(storeName, Component [, PlaceHolder, isPure])

該方法會根據 storeName 得到 Store, 再將 Store, ComponentPlaceHolder 組合, 返回一個高階組件;

其中, PlaceHolder 爲默認展現組件 (可選), 當且僅當 init 返回 Promise 時有效, 在 Component 被插入 dom 以前, 組合後的高階組件會先展現 PlaceHolder 組件, 可用於實現 loading 之類的效果;

但組件過大時, 能夠經過設置 isPure 爲 true 來提升性能, 當設置 isPure 爲 true 時, 只有 dispatch 方法能觸發組件的 render, 我相信這比經過在 shouldComponentUpdate 裏寫 shallowEqual 要有效得多;

也能夠經過下面的 configure 設置默認的 isPure 爲 true;

進階使用

dispatcher(action, ...args)

dispatch 的高階函數; 例:

dispatch('counter::add', 1);
等同於: dispatcher('counter::add')(1);

dispatch('test::testAdd', 1, 2, 3, 4);
等同於: dispatcher('test::testAdd', 1, 2)(3, 4);

configure(option)

使用 nearly 進行開發, 咱們須要考慮 storeName 重複的狀況, 我推薦經過將 storeName 映射文件路徑的方式來避免;

nearly 提供了兩個可供配置的方法: beforeConnectbeforeDispatch;

  • 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`));
        }
    }
});

同一 Store 單實例使用

在業務中咱們常常須要跨組件通訊, 或者組件間共享數據;

使用 Nearly 咱們能很輕易地將兩個不一樣的組件綁定相同的 Store, 只要傳入 connectstoreName 是相同的便可;
例: 簡單的輸入同步顯示

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 多實例使用

咱們開發通用組件時會須要給同一組件綁定同一 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

示例

Tips

  1. nearly-config.js 必須在業務邏輯以前加載;

  2. 雖然有 registerStore API, 不過做者仍是推薦使用 connect 來隱式註冊 Store, 由於 connect 經過 storeName 映射文件的方式來註冊 Store, 在確保惟一性的同時更容易維護和 debug;

  3. 在 Nearly 中對 Promise 的判斷是不許確的 (只要有 then 方法均認爲是 Promise 實例) , 一方面是由於 Nearly 中只使用了 then 方法, 另外一方面是爲了兼容 jQuery.Deferred 等類庫;

  4. 歡迎提 issue 或是 pr;

相關文章
相關標籤/搜索