您现在的位置是:首页 > 文章详情

flux架构,是我理解错了吗?记一次用ts造flux轮子的经历。

日期:2018-06-10点击:334

flux架构,是我理解错了吗?

笔者原意是希望能通过阅读redux文档和其源码彻底掌握其设计思想和使用技巧。一开始一头雾水不清楚为什么这样设计,最后还是一头雾水不清楚为什么这样设计?

一、flux的基本概念

以下内容引用自(阮一峰老师的博客文章)[http://www.ruanyifeng.com/blog/2016/01/flux.html]

首先,Flux将一个应用分成四个部分。

  • View: 视图层
  • Action(动作):视图层发出的消息(比如mouseClick)
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
img

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 
f5575e91a85c26dca958a45bc89b654f1a45d96c


这是我自定义的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

原文作者:雕刻零碎

本文来源开源中国如需转载请紧急联系作者


原文链接:https://yq.aliyun.com/articles/610264
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章