JavaScript 釋出-訂閱設計模式實現 React EventBus(相當於vue的$Bus)非父子之間通訊

有隻橘貓發表於2023-04-18

提前宣告: 我沒有對傳入的引數進行及時判斷而規避錯誤,僅僅對核心方法進行了實現;

解決了react的非父子間的通訊;

參考文件:https://github1s.com/browserify/events/blob/main/events.js

                 https://www.npmjs.com/package/events

                 https://github.com/browserify/events

                 

1.其中的一種實現的方式   

 

首先先新建一個檔案eventBus.tsx

class EventBus {
  constructor() {
    this.events = this.events || new Map(); // 儲存事件/回撥鍵值對
    this.maxListeners = this.maxListeners || 10; // 設立監聽上限
  }

  // 觸發名為type的事件
  emit(type, ...args) {
    let handler = null;
    handler = this.events.get(type);
    console.log('? ~ file: eventBus.js:11 ~ EventBus ~ emit ~ handler:', handler, args);
    if (handler === undefined) {
      return false;
    }
    if (Array.isArray(handler)) {
      // 如果是一個陣列說明有多個監聽者,需要依次此觸發裡面的函式
      // eslint-disable-next-line no-plusplus
      for (let i = 0; i < handler.length; i++) {
        if (args.length > 0) {
          handler[i].apply(this, args);
        } else {
          handler[i].call(this);
        }
      }
    } else {
      // 單個函式的情況我們直接觸發即可
      // eslint-disable-next-line no-lonely-if
      if (args.length > 0) {
        handler.apply(this, args);
      } else {
        handler.call(this);
      }
    }
    return true;
  }

  // 監聽名為type的事件
  on(type, fn) {
    const handler = this.events.get(type);
    if (!handler) {
      this.events.set(type, fn);
    } else if (handler && typeof handler === 'function') {
      // 如果handler是函式說明只有一個監聽者
      this.events.set(type, [handler, fn]); // 多個監聽者我們需要用陣列儲存
    } else {
      handler.push(fn); // 已經有多個監聽者,那麼直接往陣列裡push函式即可
    }
  }

  // eslint-disable-next-line consistent-return
  remove(type, fn) {
    const handler = this.events.get(type); // 獲取對應事件名稱的函式清單
    if (handler && typeof handler === 'function') {
      // 如果是函式,說明只被監聽了一次
      this.events.delete(type, fn);
    } else {
      if (handler === undefined) {
        return false;
      }
      let position = null;
      // 如果handler是陣列,說明被監聽多次要找到對應的函式
      // eslint-disable-next-line no-plusplus
      for (let i = 0; i < handler.length; i++) {
        if (handler[i] === fn) {
          position = i;
        } else {
          position = -1;
        }
      }
      // 如果找到匹配的函式,從陣列中清除
      if (position !== -1) {
        // 找到陣列對應的位置,直接清除此回撥
        handler.splice(position, 1);
        // 如果清除後只有一個函式,那麼取消陣列,以函式形式儲存
        if (handler.length === 1) {
          this.events.set(type, handler[0]);
        }
      } else {
        return this;
      }
    }
  }
}

const eventBus = new EventBus();
export default eventBus;


// 簡單實現的釋出訂閱模式 也是對的
// class EventEmitter {
//   constructor() {
//     this.eventBus = this.eventBus || {};
//   }

//   emit(type, params) {
//     this.eventBus.type.forEach(item => {
//       item(params);
//     });
//   }

//   on(type, fn) {
//     if (this.eventBus.type) {
//       this.eventBus.type.push(fn);
//     } else {
//       this.eventBus.type = [fn];
//     }
//   }

//   remove(type, fn) {
//     if (this.eventBus[type]) {
//       delete this.eventBus.type
//     }
//   }
// }

// const eventBus = new EventEmitter();
// export default eventBus;

然後再元件A使用=>接收

import React, { useState, useEffect, useCallback } from 'react';
// import eventBus from '@/utils/events';
import eventBus  from '@/utils/eventBus';

const TopBox = () => {
  const [num, setNum] = useState(0);

  const addNum = useCallback((message: any) => {
    setNum(message);
  }, []);

  useEffect(() => {
    eventBus.on('emit', addNum);
    return () => {
      if(eventBus) {
        eventBus.remove('emit', addNum);
      }
    };
  }, []);

  return (
    <div>
      <span>我也不知道是什麼111: {num}</span>
    </div>
  );
};

export default TopBox;

然後再元件B使用=>觸發

import React, { useState } from 'react';
import { Button } from 'antd';
// import eventBus from '@/utils/events';
import eventBus from '@/utils/eventBus';

const TopBox = () => {
  const [num,setNum] = useState(0)

  const addNum = () => {
    const numNew = num + 1;
    setNum(numNew);
    eventBus.emit('emit', numNew)
  }

  return (
    <div>
      <Button onClick={() => {addNum()}}>按鈕</Button>
      <span>我也不知道是什麼:{num}</span>
    </div>
  );
};

export default TopBox;

 

 

2.其中的另一種實現的方式

安裝這個events外掛

yarn add events

新建一個檔案evnets.ts

import { EventEmitter } from 'events';
export default new EventEmitter();

元件A使用

import React, { useState, useEffect, useCallback } from 'react';
import eventBus from '@/utils/events';
// import eventBus  from '@/utils/eventBus';

const TopBox = () => {
  const [num, setNum] = useState(0);

  const addNum = useCallback((message: any) => {
    setNum(message);
  }, []);

  useEffect(() => {
    eventBus.on('emit', addNum);
    return () => {
      if(eventBus) {
        // eventBus.remove('emit', addNum);
        eventBus.removeListener('emit', addNum);
      }
    };
  }, []);

  return (
    <div>
      <span>我也不知道是什麼111: {num}</span>
    </div>
  );
};

export default TopBox;

元件B使用

import React, { useState } from 'react';
import { Button } from 'antd';
import eventBus from '@/utils/events';
// import eventBus from '@/utils/eventBus';

const TopBox = () => {
  const [num,setNum] = useState(0)

  const addNum = () => {
    const numNew = num + 1;
    setNum(numNew);
    eventBus.emit('emit', numNew)
  }

  return (
    <div>
      <Button onClick={() => {addNum()}}>按鈕</Button>
      <span>我也不知道是什麼:{num}</span>
    </div>
  );
};

export default TopBox;

 

相關文章