flux架構,是我了解錯了嗎?
筆者原意是希望能通過閱讀redux文檔和其源碼徹底掌握其設計思想和使用技巧。一開始一頭霧水不清楚為什麼這樣設計,最後還是一頭霧水不清楚為什麼這樣設計?
一、flux的基本概念
以下内容引用自(阮一峰老師的部落格文章)[
http://www.ruanyifeng.com/blog/2016/01/flux.html]
首先,Flux将一個應用分成四個部分。
- View: 視圖層
- Action(動作):視圖層發出的消息(比如mouseClick)
- Dispatcher(派發器):用來接收Actions、執行回調函數
- Store(資料層):用來存放應用的狀态,一旦發生變動,就提醒Views要更新頁面
Flux 的最大特點,就是資料的"單向流動"。
- 使用者通路 View
- View 發出使用者的 Action
- Dispatcher 收到 Action,要求 Store 進行相應的更新
- Store 更新後,發出一個"change"事件
- 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。為什麼要這樣做呢?
疑問如下:
- 為什麼非要用一個巨大的Store存起來?
- Actions真的是View的附屬品嗎?
- 将樹狀的Components樹投射到沒有明顯層級的Stroe真的合理嗎?
我的回答是:
- 并非必要,不同的Component可以有不用的Stroe
- Actions 不僅僅是View的附屬品,其中還可以進一步劃分
- 不合理,允許并設計不同層級的可組合性是必要的。
吐槽: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架構。
若産生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
原文作者:雕刻零碎
本文來源
開源中國如需轉載請緊急聯系作者