react 中發佈訂閱模式使用

react 中發佈訂閱模式使用

場景

怎麼能將設計模式應用到我們的 React 項目中?以前一直在思考這個問題。

場景一

模塊 A 模塊 B 需要用到同一個數據 data,A 和 B 都會修改這份數據,且這兩個模塊會同時存在;這時我們如何做到數據公用與各個模塊的更新?

方案一:
將這份數據作爲公共的數據 data,A B 模塊同時使用並更改這份數據這一份數據。若使用 redux 代碼可能是這樣:

// store

const store = {
    common: { data: [] },
    A: {},
    B: {},
};

// reducer
function commonReducer(state = { data: [] }, action) {
    switch (action.type) {
        case 'common_setData': {
            return {
                ...state,
                data: action.data,
            };
        }
        default:
            return state;
    }
}

// connect

const actionCreator = () => {};

connect(({ A, common }) => ({ ...A, data: common.data }))(A);
connect(({ B, common }) => ({ ...A, data: common.data }))(B);

// change
// A B change調用方法;
this.props.dispatch({
    type: 'common_setData',
    data: [1, 2],
});

好的,第一種場景可以使用 redux 完美解決

方案二:待補充

場景二

A 模塊使用了 data1, B 模塊使用了 data2;A B 模塊可以修改對應的 data;這兩份 data 結構上不同,但是存在業務上的聯繫: 當 data1 更新後需要 data2 更新;data2 更新同樣需要 data1 同步;對應後端的兩個不同的 API。

我們整理一下

  • A B 使用兩份存在聯繫的 data
  • 其中一個更新需要另一個更新
  • 兩份 data 對應不同的 API 接口
  • A B 對應兩個不同的 tab 且可能同時存在

方案一

當其中一個數據因操作發生更新時,判斷另一個模塊是否存在 如果存在則調用他的數據更新邏輯;

如果你使用了 redux,可能方便一點:

// reducerA
// 省略B
function reducerA(state = { data: [] }, action) {
    switch(action.type) {
        case 'A_setDataA': {
            return {
                ...state,
                data: action.data
            }
        }
        default: return state
    }
}

// 假設使用了thunk中間件
const queryA = () => async (dispatch, getState) => {
    const dataA = await API.queryA()
    dispatch({
        type: 'A_setDataA'
        data: dataA
    })
}

// page

class B extends React.Component {
    handleUpdateData = () => {
        // 如果 A模塊存在
        const { isAExistFlag, dispatch, queryA, queryB } = props
        dispatch(queryB())
        if (isAExistFlag) {
            dispatch(queryA())
        }
    }
}

這樣利用了 redux 可以實現功能,在模塊 B 內調用模塊 A 的更新邏輯;但這樣邏輯就耦合了,我在模塊 A 調用模塊 B 方法 在模塊 B 調用模塊 A 的方法;但很有可能這兩個模塊是沒有其他交互的。這違反了低耦合高內聚的原則
而且書寫 redux 的一個原則就是 不要調用(dispatch)其他模塊的 action

如果你不使用 redux 如果是一個模塊內調用其他模塊的方法也是沒有做到解耦的;那如何做到解耦尼?請看方案二

方案二:利用事件系統

如果您的項目中沒有一個全局的事件系統,可能需要引入一個;一個簡單的事件系統大概是:

class EventEmitter {
    constructor() {
        this.listeners = {};
    }

    on(type, cb, mode) {
        let cbs = this.listeners[type];
        if (!cbs) {
            cbs = [];
        }
        cbs.push(cb);
        this.listeners[type] = cbs;
        return () => {
            this.remove(type, cb);
        };
    }

    emit(type, ...args) {
        console.log(
            `%c event ${type} be triggered`,
            'color:rgb(20,150,250);font-size:14px',
        );
        const cbs = this.listeners[type];
        if (Array.isArray(cbs)) {
            for (let i = 0; i < cbs.length; i++) {
                const cb = cbs[i];
                if (typeof cb === 'function') {
                    cb(...args);
                }
            }
        }
    }

    remove(type, cb) {
        if (cb) {
            let cbs = this.listeners[type];
            cbs = cbs.filter(eMap => eMap.cb !== cb);
            this.listeners[type] = cbs;
        } else {
            this.listeners[type] = null;
            delete this.listeners[type];
        }
    }
}

export default new EventEmitter();

這個事件系統具有註冊,發佈,移除事件的功能。那我們怎麼在剛纔這個場景去使用它尼?

  1. 發佈:當A模塊內數據因操作發生變化時,觸發該數據變化的事件,定義typedata1Change
  2. 註冊:這裏B模塊的註冊的時機,上述的場景爲A和B模塊可能同時出現,所以A模塊存在B模塊卻不存在。所以這個B模塊事件的監聽選擇在B模塊組件的componentDidMount的時候註冊,在componentWillUnmount時移除

大致的代碼如下:

import EventEmitter from 'eventEmitter'
class A extends React.Component {
    handleUpdateData = () => {
        // 如果 A模塊存在
        const { dispatch, queryB } = props
        dispatch(queryA())
        EventEmitter.emit('data1Change')
    }
}

// B
import EventEmitter from 'eventEmitter'
class B extends React.Component {
    componentDidMount() {
        const unlistener = EventEmitter.on('data1Change', this.handleData1Change)
    }

    componentWillUnmount() {
        EventEmitter.on('data1Change', this.handleData1Change)
    }

    handleData1Change = () => {
        const { dispatch, queryB } = this.props
        dispatch(queryB())
    }
}

這樣通過事件系統做到了兩個模塊之間的解耦,作爲事件發佈方只管發佈自己的事件。兩個模塊在事件系統唯一的聯繫就是事先定義好事件的type。

不過這也增加了幾行的代碼量,但相比帶來的優勢來說可以不計。

其他方案歡迎大家評論

其他場景

待大家補充

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章