flux架构,是我理解错了吗?记一次用ts造flux轮子的经历。
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
原文作者:雕刻零碎
本文来源开源中国如需转载请紧急联系作者
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
以太坊(ethereum)技术开发相关资料
收集所有以太坊(ethereum)技术开发相关资料 INTRO 介绍 Started 入门 区块链技术指南 区块链领域比较系统的入门资料 什么是以太坊?什么是智能合约?以太坊智能合约入门概念 理解区块链 区块链关键要点讲解 (一)简单易懂地介绍什么是区块链 比特币区块链关键词讲解 (二)简单易懂地介绍什么是区块链(技术篇) 比特币区块链技术图解 一文看懂区块链架构设计 从技术分层解构架构 Ethereum 以太坊进阶 以太坊白皮书 The Ethereum Wiki (English) 以太坊官方文档中文版 以太坊Gas使用 Calculating Costs in Ethereum Contracts (English) 以太坊代码剖析 以太坊源码阅读 TUTORIAL 教程 Ethereum 以太坊 ethbox,一键安装以太坊本地开发环境-windows 以太坊DApp开发入门教程-投票系统 以太坊教程 以太坊Dapp与智能合约开发实战入门 以太坊电商教程 以太坊IPSF、Node.js、Mongodb构建电商平台实战 web3j教程 java和android开发以太坊实战教程 ...
- 下一篇
12因子应用
12因子应用是由PaaS提供商Heroku的Adam Wiggins提出的。Heroku已经被SaaS行业领军厂商Salesforce收购,根据维基百科的记载,同年2011年松本行弘也加盟了该公司担任Ruby首席架构师。 作为云平台厂商,该公司创始人根据经验总结出来的SaaS软件开发的方法论(12因子)无疑值得同行学习。引用原文的描述: 本文综合了我们关于 SaaS 应用几乎所有的经验和智慧,是开发此类应用的理想实践标准,并特别关注于应用程序如何保持良性成长,开发者之间如何进行有效的代码协作,以及如何 避免软件污染 。 12因子的大纲已经有中文翻译,可以直接参考 https://12factor.net/zh_cn/ 每个因子的解释,可以在该网站下载epub英文电子版了解。其基本准则跟我一直在考虑的如何构建健壮和运维友好的系统有一定重合,
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境