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

简介:

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


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

原文作者:雕刻零碎

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


相关文章
|
11天前
|
存储 前端开发 调度
Flux 与传统的 MVC 架构模式区别
Flux是一种用于构建用户界面的架构模式,与传统的MVC架构不同,它采用单向数据流,通过Dispatcher统一管理数据的分发,Store负责存储数据和业务逻辑,View只负责展示数据,使得应用状态更加可预测和易于维护。
|
11天前
|
存储 前端开发 数据可视化
在实际项目中,如何选择使用 Flux 架构或传统的 MVC 架构
在实际项目中选择使用Flux架构或传统MVC架构时,需考虑项目复杂度、团队熟悉度和性能需求。Flux适合大型、高并发应用,MVC则适用于中小型、逻辑简单的项目。
|
20天前
|
存储 JavaScript 前端开发
Flux 架构模式和 Redux 区别
Flux架构模式和Redux都是前端状态管理工具,Flux强调单向数据流,通过Dispatcher分发Action到Store,再由View更新;Redux则简化了这一流程,使用单一的全局Store,通过Reducer纯函数处理状态变更,使状态管理更加集中和可预测。
|
20天前
|
存储 前端开发 JavaScript
Flux 架构模式
Flux 是一种用于构建用户界面的架构模式,主要用于管理应用程序的状态。它通过单向数据流将应用的不同部分(视图、存储和调度器)解耦,确保状态更新的可预测性和数据的一致性。
|
22天前
|
缓存 前端开发 JavaScript
前端架构思考:代码复用带来的隐形耦合,可能让大模型造轮子是更好的选择-从 CDN 依赖包被删导致个站打不开到数年前因11 行代码导致上千项目崩溃谈谈npm黑洞 - 统计下你的项目有多少个依赖吧!
最近,我的个人网站因免费CDN上的Vue.js包路径变更导致无法访问,引发了我对前端依赖管理的深刻反思。文章探讨了NPM依赖陷阱、开源库所有权与维护压力、NPM生态问题,并提出减少不必要的依赖、重视模块设计等建议,以提升前端项目的稳定性和可控性。通过“left_pad”事件及个人经历,强调了依赖管理的重要性和让大模型代替人造轮子的潜在收益
|
存储 Prometheus 监控
微服务轮子项目(08) - 监控架构设计Metrics
微服务轮子项目(08) - 监控架构设计Metrics
70 0
|
缓存 前端开发 安全
微服务轮子项目(06) - 服务认证架构设计(URL级权限控制)
微服务轮子项目(06) - 服务认证架构设计(URL级权限控制)
83 0
|
NoSQL Redis 微服务
微服务轮子项目(05) - 服务认证架构设计(token自动续签)
微服务轮子项目(05) - 服务认证架构设计(token自动续签)
66 0
|
NoSQL API Redis
微服务轮子项目(04) - 服务认证架构设计(无网络隔离)
微服务轮子项目(04) - 服务认证架构设计(无网络隔离)
118 0
|
负载均衡 API 开发工具
微服务轮子项目(03) - 服务认证架构设计(有网络隔离)
微服务轮子项目(03) - 服务认证架构设计(有网络隔离)
70 0