37行程式碼構建無狀態元件通訊工具-讓惱人的Vuex和Redux滾蛋吧!

YaHuiLiang(Ryou)發表於2018-11-03

狀態管理的現狀

很多前端開發者認為,VuexRedux是用來解決元件間狀態通訊問題的,所以大部分人僅僅是用於達到狀態共享的目的。但是通常Redux是用於解決工程性問題的,用於分離業務與檢視,讓結構更加清晰,從而達到易於維護的目的。也就是 Flux(這裡我之前翻譯的Flux深度解讀)架構所解決的問題。但是絕大多數時候,大家只是想解決的問題是元件巢狀過深的時候,如何將子元件的狀態直接傳遞給父元件。那麼此時Vuex也好Redux也好,對於我們的訴求就過於繁瑣。每次通訊後,我們還需要清理掉Store中的狀態。更加惱人的是,我們該如何選擇哪些狀態應該放入Store,那些狀態應該放在組建內的state一直困擾著大家,甚至於社群也是沒有一個定論。因此很多年輕前端工程師所開發的專案,狀態管理極其混亂。以至於不久後就難以維護。

無狀態元件間通訊的由來

針對以上訴求,我們能不能開發一個簡單的元件間通訊工具來解決目前前端狀態管理的痛點呢?因此我實現了一個無狀態元件通訊工具,這也就是這篇文章的由來。

無狀態,也就是它並不關注資料內容,它只是起到一個管道的作用,在元件間建立管道,元件可以通過該管道向管道另一頭的元件說:“hello world!This is your message。”。

巧用設計模式

設計模式,大家都很熟悉,現代前端框架已經使用非常多的設計模式,大家都能耳熟能詳的就是觀察者模式裝飾器模式,以及釋出訂閱模式(一種將觀察者和通知者融合的設計模式)。

設計模式,是用於解決特定問題而被大家公認為最佳實踐的模式。一般最被大家熟知的為23種設計模式 - 這裡是我用ES2015實現的物件導向方式的設計模式例子

那麼我們該如何利用設計模式解決我們的問題呢?上程式碼:

    const listener = {}; // 用於儲存訂閱者
    
    // 註冊訂閱者
    function subscribe (event, handle) {
      // 訂閱者訂閱的資訊
      if (typeof event !== 'string') {
        throw new Error('event must be String!');
      }
      // 訂閱者的callback函式
      if (typeof handle !== 'function') {
        throw new Error('handle must be function!');
      }
      // 將訂閱者新增到訂閱者容器中儲存起來
      if (!listener[event]) {
        listener[event] = [];
        listener[event].push(handle);
      } else {
        var index = listener[event].indexOf(handle);
        if (index < 0) {
          listener[event].push(handle);
        }
      }
      // 返回用於取消訂閱的介面,這裡是一個高階函式
      return function unSubscribe() {
        var index = listener[event].indexOf(handle);
        if (index > -1) {
          listener[event].splice(index, 1);
        }
      }
    }
    // 為通知者提供的發起通知的介面
    function dispatch (event, payload) {
      if (listener[event]) {
        listener[event].forEach(function serviceFunc(handle) {
          handle(payload);
        })
      } else {
        throw new Error('No subscriber be registried for serviceName!');
      }
    }
    
    export {
      subscribe,
      dispatch
    }
複製程式碼

這裡主要使用了一下幾種JS語言常用的設計模式以及技術知識點:

  • 沙盒模式 在之前一篇文章如何構建一個不到100行的小程式端mini版本redux 中介紹瞭如何通過沙盒模式構建一個mini小程式版的redux。如果對於沙盒模式還不瞭解可以參看這篇文章,這裡用沙盒模式對用於儲存訂閱者的變數進行封裝和保護。
  • 釋出訂閱模式 釋出(dispatch)訂閱(subscribe)模式是一種混合模式,它包含了觀察者模式和通知者模式。
  • 高階函式 這是JS一種常見的知識點,在面試的時候經常會有面試官提問這個技術,但是真正用於實戰的並不多,大多都是構建基礎架構的高階工程師才有機會使用。

以上,我們利用沙盒模式,釋出訂閱模式實現了一個基本的無狀態元件間通訊工具。那麼我們如何使用它呢?

使用無狀態工具實現元件間資料通訊

下面是我們要實現的一個例子:

37行程式碼構建無狀態元件通訊工具-讓惱人的Vuex和Redux滾蛋吧!

元件結構是 爺爺包含兒子,兒子包含孫子,兒子和孫子可以和爺爺直接對話。

在根元件(爺爺元件)註冊訂閱者用來訂閱兒子和孫子發來的資訊:

    import Son from './Son';
    
    import { subscribe } from './utils';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          messageFromSon: '',
          messageFromGrandson: ''
        }
        // 在這裡訂閱了兒子的會話和孫子的會話,記得bind(this)這樣才能訪問元件的上下文
        this.listenSonHandle = this.listenSonHandle.bind(this);
        this.listenGrandsonHandle = this.listenGrandsonHandle.bind(this);
        // 我們需要保留訂閱會話,在不需要的時候取消註冊
        this.listenHandle = [
          subscribe('son', this.listenSonHandle),
          subscribe('grandson', this.listenGrandsonHandle)
        ]
      }
    
      listenSonHandle(payload) {
        this.setState({
          messageFromSon: payload
        });
      }
    
      listenGrandsonHandle(payload) {
        this.setState({
          messageFromGrandson: payload
        })
      }
    
      componentWillUnmount() {
        this.listenHandle.forEach((unSubscribe) => {
          unSubscribe();
        })
      }
    
      render() {
        return (
          <div style={{background: 'red'}}>
            <Son />
            <div>
              兒子來電:{this.state.messageFromSon}
            </div>
            <div>
              孫子來電:{this.state.messageFromGrandson}
            </div>
          </div>
        );
      }
    }
複製程式碼

兒子元件需要和爺爺元件直接對話,那麼就需要和爺爺元件建立相同的通訊管道:

    import React from 'react';
    import { dispatch } from './utils';
    import Grandson from './Grandson';
    
    export default class Son extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          message: ''
        }
      }
    
      render() {
        return <div style={{background: 'green'}}>
          這裡是兒子:
          <input value={this.state.message} onChange={(e) => {
            this.setState({
              message: e.target.value
            })
          }}></input>
          <button onClick={() => {
            // 利用通知者介面,向爺爺元件傳送資訊
            dispatch('son', this.state.message);
          }}>告訴老子</button>
          <Grandson/>
        </div>
      }
    }
複製程式碼

孫子元件想要向爺爺元件傳送資訊,如果不使用redux的話就要一層一層的傳遞props。先告訴爸爸,然後爸爸告訴爺爺,但是有了我們現在構建的無狀態元件通訊工具。就不需要那麼麻煩了:

    import React from 'react';
    import { dispatch } from './utils';
    
    export default class Son extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          message: ''
        }
      }
    
      render() {
        return <div style={{background: 'yellow'}}>
          這裡是孫子:
          <input value={this.state.message} onChange={(e) => {
            this.setState({
              message: e.target.value
            })
          }}></input>
          <button onClick={() => {
            dispatch('grandson', this.state.message);
          }}>告訴爺爺</button>
        </div>
      }
    }
複製程式碼

例子原始碼

甚至我們可以很容易再剝離出一層業務層,實現業務與檢視的隔離。起到和Vuex,Redux同樣的目的。

最後

由於設計模式是語言無關的,因此這個utils/index.js下的程式碼是可以用於任何前端框架的。

這就是設計模式的強大之處。是不是你們可以扔掉那惱人的Vuex和Redux了呢?

廣告:我們團隊招人,途家網,地點在國家會議中心,我們的團隊很年前,才元件1年多。有很多機會。

別人面試造火箭,進去擰螺絲。我們面試擰螺絲,進來造火箭。我們招不起能面試造火箭的人(T_T不知道這麼說老大會不會打我),所以只能招得起面試能擰螺絲的。但是我們有很多造火箭的需求。

如果你想尋求刺激,就來加入我們吧

yahuil_1@tujia.com

相關文章