天天看點

flux架構,是我了解錯了嗎?記一次用ts造flux輪子的經曆。flux架構,是我了解錯了嗎?

flux架構,是我了解錯了嗎?

筆者原意是希望能通過閱讀redux文檔和其源碼徹底掌握其設計思想和使用技巧。一開始一頭霧水不清楚為什麼這樣設計,最後還是一頭霧水不清楚為什麼這樣設計?

一、flux的基本概念

以下内容引用自(阮一峰老師的部落格文章)[

http://www.ruanyifeng.com/blog/2016/01/flux.html

]

首先,Flux将一個應用分成四個部分。

  • View: 視圖層
  • Action(動作):視圖層發出的消息(比如mouseClick)
  • Dispatcher(派發器):用來接收Actions、執行回調函數
  • Store(資料層):用來存放應用的狀态,一旦發生變動,就提醒Views要更新頁面
flux架構,是我了解錯了嗎?記一次用ts造flux輪子的經曆。flux架構,是我了解錯了嗎?

Flux 的最大特點,就是資料的"單向流動"。

  1. 使用者通路 View
  2. View 發出使用者的 Action
  3. Dispatcher 收到 Action,要求 Store 進行相應的更新
  4. Store 更新後,發出一個"change"事件
  5. View 收到"change"事件後,更新頁面

上面過程中,資料總是"單向流動",任何相鄰的部分都不會發生資料的"雙向流動"。這保證了流程的清晰。

二、對flux的反思

單向資料流既然是這裡的核心,那不禁要問,實作單向資料流的關鍵點在于什麼?**Dispatcher!!**統一的分發器能夠使得控制資料的流動!!

Q:但是,dispatcher是什麼?
A:統一的分發器。
Q:分發啥?
A:分發Actions事件。
Q:Actions事件是什麼?
A:視圖層發出的消息。
Q:但是我那麼多元件,有那麼多Actions,命名沖突了怎麼辦?我怎麼知道那個元件對應哪幾個Actions?編寫Actions 時候怎麼知道對應資料庫結構?
A:你怎麼那麼多問題!
           

flux 架構統一了将多層級Components 投射到扁平的 Store結構中。使用者需要預定義Store的結構,使得初始化Flux的時候就完整知道Store的結構。

但是這樣好嗎?筆者覺得這樣喪失了擴充性,有時候元件的臨時變量丢進去也會常駐在對象中。Redux 的 Dispatcher 實作直接是用大switch結構組合而成,需要每次疊代判斷觸發Actions。為什麼要這樣做呢?

疑問如下:

  1. 為什麼非要用一個巨大的Store存起來?
  2. Actions真的是View的附屬品嗎?
  3. 将樹狀的Components樹投射到沒有明顯層級的Stroe真的合理嗎?

我的回答是:

  1. 并非必要,不同的Component可以有不用的Stroe
  2. Actions 不僅僅是View的附屬品,其中還可以進一步劃分
  3. 不合理,允許并設計不同層級的可組合性是必要的。

吐槽:Redux用函數編寫的結構真的很難受,它的d.ts檔案簡直就是需要那麼複雜嗎!

三、個人對flux架構的了解

Store:

1.作為最底層資料層DataBase,應有CURD接口或者指令。
2.中間層是Actions,對應的資料庫概念應該是“事務”。就是該“Actions"事務需要修改DataBase的什麼資料,如何處理修改的異常等問題。
3.Dispatcher實作,是提供Action的名稱“Actionxxx”,調用參數,來統一接口調用Actions。
4.産生新Store通過繼承對應的抽象類(本質是原型模式)
5.父子關系的建立,需要在Store的構造函數中調用内置方法。
6.父子關系的解綁同上。
           

Dispatcher:

必然依附于Store,
有同一的實作,可重寫,
裡面内置中間件調用接口的實作以及Store事件的調用接口的實作。
           

View:

樹級元件,
局部Store按需建立,按需建立父子連接配接。
View層的事件Event觸發 = 應同時調用對應的Actions,支援異步函數。

           

Actions:

割離為
View的Events 與 Stores的Actions
           
flux架構,是我了解錯了嗎?記一次用ts造flux輪子的經曆。flux架構,是我了解錯了嗎?

這是我自定義的flux架構。

若産生Store的父子層級,則dispatcher的最後會溯源執行EventPool。

可以繼承Store的類以重載Actions,Dispatcher

四、typescript 的簡單實作。

export interface ReducerFunc{
 (prevState:any,args:any):stateType
}
export type stateType = Object & {
 [key:string]:any;
 [key:number]:any;
}
export interface actionTypes{
 type:string;
 [key:string]:any;
}
export interface EventFunc{
 (prevState:any,NewState:any,...args:any[]):void;
 (prevState:any,NewState:any,actionTypes:actionTypes):void;
 $TOKEN?:number;
 unsubscribe?:()=>void;
}
export interface EventMap{
 [token:number]:EventFunc
}
export class Event{
 private evtMap:EventMap = {}
 public token = -1;
 public subscribe(callback:EventFunc){
 const $TOKEN = this.token++;
 callback.$TOKEN = $TOKEN
 callback.unsubscribe = ()=>{
 this.unsubscribe($TOKEN)
 }
 return this.evtMap[$TOKEN] = callback
 }
 public unsubscribe(token:number){
 delete this.evtMap[token];
 return this
 }

 // 可複用的重載接口 public async run(obj:any,NewState:any,...args:any[]):Promise<true>;
 public async run(obj:any,NewState:any,actionTypes:actionTypes){
 let key:string = "" try{
 for (key in this.evtMap) {
 await this.evtMap[key].bind(obj)(NewState,actionTypes)
 }
 return true
 }catch(e){
 throw Error(`事件錯誤:${e},TOKEN:${key}`)
 }
 }
}

export abstract class DataBase{
 protected state:stateType;
 public evt:Event = new Event();
 public abstract _name:string;
 public _parent?:DataBase;
 [ActionName:string]:ReducerFunc | Promise<ReducerFunc> | any

 public constructor(state?:stateType){
 this.state = state || {};
 }
 public subscribe(callback:EventFunc){
 return this.evt.subscribe(
 callback.bind(this)
 )
 }
 public unsubscribe(tokenOrCallback:number|EventFunc){
 if (typeof tokenOrCallback === "number"){
 return this.evt.unsubscribe(tokenOrCallback)
 }else{
 // if (tokenOrCallback.$TOKEN){ //不進行判斷,僅靠規範 return this.evt.unsubscribe(tokenOrCallback.$TOKEN as number)
 // } // return this.evt
 }
 }

 public getState(){ return this.state }

 // 可複用的重載接口 public async dispatch(this:DataBase,...argv:any[]):Promise<stateType>; 
 public async dispatch(this:DataBase,actionTypes:actionTypes):Promise<stateType>{
 //===============Middleware==========
 MiddleWare.list.forEach((value:Function)=>{
 value.apply(this)
 });

 //================異步/同步Action================ let NewState:stateType
 try{
 NewState = await this[actionTypes.type](actionTypes) 
 }catch(e){
 return Error("Active Action Error:"+e)
 }
 let obj:DataBase = this //===============父子事件==================== try{
 let res = await obj.evt.run(obj,NewState,actionTypes);//接口,允許侵入性修改 // if (res != true){ console.warn("Unexcepted Event") } while(obj._parent !== undefined){
 obj = obj._parent
 res = await obj.evt.run(obj,NewState,actionTypes);//接口,允許侵入性修改 // if (res != true){ console.warn("Unexcepted Event") }
 }
 }catch(e){
 return Error(`${e},Active OBJ:${obj._name}`)
 }
 //==================成功傳回值======================== return (this.state = NewState)
 }

 //=====================父子關系的實作================== protected attach(parentClass:DataBase){
 this._parent = parentClass
 Object.defineProperty(
 this._parent.state,this._name,{
 get:()=>{
 return this.state
 },
 configurable:true,
 enumerable:true // ,set:function(value:stateType){ // return modelIns.state = value // }
 }
 );
 }

 // 手動釋放記憶體,删除父子聯系 public Unlink(){
 if (this._parent !== undefined){
 delete this._parent.state[this._name]
 }
 }
}

export namespace MiddleWare{
 export let list:Function[] = []
 export function add(func:Function){
 list.push(func)
 }
}
           

五、簡單的Demo

import * as data from ".";
function sleep(d:number){
 for(var t = Date.now();Date.now() - t <= d;);
 }

export class Store extends data.DataBase{
 _name:string = "Store";
 public Add(actionType:data.actionTypes){
 this.state["type"] = actionType["args"]
 }
}
export const Global = new Store();
Global.subscribe(function(){
 console.log(Global.getState())
})
export class Model extends data.DataBase{
 _name:string = "Model";

 constructor(){
 super();
 // this._name = ... //動态_name,需要配合構造函數實作 this.attach(Global) //指派父類
 }
 public Add(actionType:data.actionTypes):data.stateType{
 let newState = Object.assign({},this.state)
 newState["type"] = actionType["args"]
 console.log("=======Add======")
 return newState
 }
 public async NULL(actionType:data.actionTypes):Promise<data.stateType>{
 let newState = Object.assign({},this.state)
 newState["type"] = undefined;
 sleep(3000)
 console.log("======NULL=======")
 return newState
 }
}
export const Local = new Model();
Local.subscribe(async function(prevState:any,NewState:any,actionType:data.actionTypes){
 console.log(prevState,NewState,actionType)
 sleep(5000)
 // throw Error("BF")
})

// Local.dispatch("Add",1) // Local.dispatch("Add",2) async function main(){
 await Local.dispatch({
 type:"Add",
 args:1
 });
 await Local.dispatch({
 type:"NULL",
 args:2
 });
 //清空對象的方法,需要被清空的對象,應隻是本子產品的局部/臨時資料區,而不應該被export,否則設定為undefined的變量不會被GC回收。
 Local.Unlink();(<any>Local) = undefined; 
 console.log(Local)
}
main()
console.log("lalalal")           

原文釋出時間:06/25

原文作者:雕刻零碎

本文來源

開源中國

如需轉載請緊急聯系作者