做者:途家梁亞輝
狀態管理的現狀
不少前端開發者認爲, Vuex
和 Redux
是用來解決組件間狀態通訊問題的,因此大部分人僅僅是用於達到狀態共享的目的。可是一般 Redux
是用於解決工程性問題的,用於分離業務與視圖,讓結構更加清晰,從而達到易於維護的目的。也就是 Flux
(這裏我以前翻譯的Flux深度解讀)架構所解決的問題。可是絕大多數時候,你們只是想解決的問題是組件嵌套過深的時候,如何將子組件的狀態直接傳遞給父組件。那麼此時 Vuex
也好 Redux
也好,對於咱們的訴求就過於繁瑣。每次通訊後,咱們還須要清理掉 Store
中的狀態。更加惱人的是,咱們該如何選擇哪些狀態應該放入 Store
,哪些狀態應該放在組件內的 state
一直困擾着你們,甚至於社區也是沒有一個定論。所以不少經驗不足的前端工程師所開發的項目,狀態管理極其混亂。以致於不久後就難以維護。前端
無狀態組件間通訊的由來
針對以上訴求,咱們能不能開發一個簡單的組件間通訊工具來解決目前前端狀態管理的痛點呢?所以我實現了一個無狀態組件通訊工具,這也就是這篇文章的由來。react
無狀態,也就是它並不關注數據內容,它只是起到一個管道的做用,在組件間創建管道,組件能夠經過該管道向管道另外一頭的組件說:「hello world!This is your message。」。面試
巧用設計模式
設計模式,你們都很熟悉,現代前端框架已經使用很是多的設計模式,你們都能耳熟能詳的就是觀察者模式,裝飾器模式,以及發佈訂閱模式(一種將觀察者和通知者融合的設計模式)。redux
設計模式,是用於解決特定問題而被你們公認爲最佳實踐的模式。通常最被你們熟知的爲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.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一種常見的知識點,在面試的時候常常會有面試官提問這個技術,可是真正用於實戰的並很少,大多都是構建基礎架構的高級工程師纔有機會使用。
以上,咱們利用沙盒模式,發佈訂閱模式實現了一個基本的無狀態組件間通訊工具。那麼咱們如何使用它呢?
使用無狀態工具實現組件間數據通訊
下面是咱們要實現的一個例子:
組件結構是 爺爺包含兒子,兒子包含孫子,兒子和孫子能夠和爺爺直接對話。
在根組件(爺爺組件)註冊訂閱者用來訂閱兒子和孫子發來的信息:
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了呢?
本文分享自微信公衆號 - 前端下午茶(qianduanxiawucha)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。