[开源]React/Vue通用的状态管理框架,不好用你来打我👀
为了防止被打,有请“燕双鹰”镇楼️♀️️️...o...
话说新冠3年,“状态管理框架”豪杰并起、群雄逐鹿,ReduxToolkit、Mobx、Vuex、Pinia、Dva、Rematch、Recoil、Zustand、Mirror...敢问英雄独钟哪厢?
Flux状态管理
笔者也用过很多态管理框架,大部分都是Flux框架
的变种,只不过加上了一些自己的糖衣和辅助方法。
- 只要糖衣做得好,省时省力人人要!
- 后面随着
Typescript
的普及,自动类型推断也是状态管理框架易用性的重要指标。
我们先简单回顾几款最主流的Flux状态管理框架的写法:
//基于Redux的Dva:
{
state(){
return {curUser: null}
},
reducers: {
setUser(state, {payload}) {
return {...state, curUser: payload}
},
},
effects: {
*login({ payload: {username, password} }, { put, call }){
const { data } = yield call(api.login, username, password);
yield put({ type: 'setUser', payload: data }); //无TS类型提示
}
}
};
//Vuex:
{
state(){
return {curUser: null}
},
mutations: {
setUser(state, curUser) {
state.curUser = curUser;
}
},
actions: {
async login({ commit }, {username, password}) {
const { data } = await api.login(username, password);
commit('setUser', data) //无TS类型提示
}
}
}
//Pinia:
{
state(){
return {curUser: null}
},
actions: {
setUser(curUser) {
this.curUser = curUser;
}
async login(username, password) {
const { data } = await api.login(username, password);
this.setUser(data) //有TS类型提示
}
}
}
果然都是一个妈生的,本质上无非就是玩3个概念:
- State
- 同步Action
- 异步Action
为Flux再添一把火
既然都是玩这3个概念,大家都容易理解,那么要自荐的Elux
就要闪亮登场了:
- Elux官网:https://eluxjs.com
- Github:https://github.com/hiisea/elux
先看它的基本用法:
class Model{
onMount() {
//初始赋值State
this.dispatch(this.actions._initState({curUser: null}));
}
@reducer //类似Vuex的mutations
setUser(curUser) {
//react中必需返回一个新state
//return {...this.state, curUser};
this.state.curUser = curUser;
}
@effect() //类似Vuex的action
async login(username, password) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
- onMount:初始化钩子,在其中完成State的初始赋值。
- reducer:React系很容易理解,Vue系可以理解为mutation,它是改变State的唯一途径。
- effect:React系很容易理解,Vue系可以理解为action,它是异步Action。
所以从糖衣语法来说Elux
其实与Dva/Vuex/Pinia也差不多,不同在于:
- Elux使用
Decorator
装饰器语法来定义reducer(mutation)
和effect(action)
,这样更简洁。 - Elux使用
Class
来组织Model,有2点好处:- 可以通过类的继承和多态来复用公共逻辑。
- 可以通过TS的类成员权限(public/private/protected)来更好的封装。
Elux特性
除了糖衣语法,Elux还有其更深层次的创新:
从图中可以看出:
- store中保存了所有state
- 每个Model管理store下的一个节点
- view从store中获取state
- dispatch(action)是触发reducer/effect的唯一途径
- reducer是纯函数,也是修改state的唯一途径
- effect可以处理任何异步操作,但不能直接修改state
- 一个action的派发类似于事件,可以触发多个reducer和effect监听
- view/effect/router都可以派发action
自动生成Action
这点类似于Pinia,不需要手动盲写类似于{type:"xxx.xxx",payload:xxxx}
这样的Action结构体,而是通过方法自动生成:
const loginAction = stageActions.login('admin','123456');
//等于{type: 'user.login', payload:{username:'admin', password:'123456'}}
dispatch(loginAction);
且具备完美的TS类型提示:
模块化
Elux使用微模块来组合应用,每个微模块对应一个业务模型Model
,每个Model
使用reducer/effect
来维护Store
下的一个节点ModuleState
。
微模块是一种前端业务模块化方案,至此不引申开来,可参见我的发文【微模块-前端业务模块化探索,拆解巨石应用的又一利器】
事件化
将action
当做Model中的事件,将reducer
、effect
当做Handler,这意味着dispatch(action)可以触发多个reducer和effect。
通过事件总线机制
,在保持各Model松散性的同时,加强Model之间的协同交互,举个例子:
假设有3个模块:user(用户模块)、article(文章模块)、my(个人中心模块)
当用户登录时,article(文章模块)需要将状态修改为可编辑,my(个人中心模块)需要获取最新通知
user/model.ts
中编写登录逻辑:
// src/modules/user/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public setUser(curUser: User) {
this.state.curUser = curUser;
}
@effect()
public async login(username: string, password: string) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
article/model.ts
中通过reducer监听setUserAction
:
// src/modules/article/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public ['user.setUser'](curUser: User) {
//根据当前用户是否登录来决定是否可编辑
this.state.editable = curUser.hasLogin;
}
}
my/model.ts
中通过effect监听setUserAction
:
// src/modules/my/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public updateNotices(notices: Notices[]) {
this.state.notices = notices;
}
@effect()
public async ['user.setUser'](curUser: User) {
if(curUser.hasLogin){
const notices = await this.api.getNotices();
this.dispatch(this.actions.updateNotices(notices));
}
}
}
user/views/Login.tsx
中派发loginAction
:
// src/modules/user/views/Login.tsx
export default ({dispatch}) => {
const login = () => {
dispatch(userActions.login('admin', '123456'));
};
return (
<div>
<button onClick={login} >登录</button>
</div>
);
}
统一化
数据模式有2大基本阵营:ImmutableData 和 MutableData。Redux是ImmutableData
阵营的代表;Vue为MutableData
的代表。
Elux可以同时兼容这2种数据模式,它们的唯一区别在reducer中:
- ImmutableData:要求返回一个新数据,不可以修改原数据。
- MutableData:可以直接修改原数据。
class Model{
@reducer
setUser(curUser) {
//vue中可以直接修改state:
this.state.curUser = curUser;
//react中必需返回一个新state
//return {...this.state, curUser};
}
}
当然,在MutableData模式下,返回一个新数据也是可以的,这为跨React和Vue项目共享Model
提供了解决方案。
await dispatch
actionHander中如果有异步操作,将返回一个promise,可以await其执行,例如:
// src/modules/user/views/Login.tsx
const onSubmit = (values: HFormData) => {
const result = dispatch(userActions.login(values));
result.catch(({message}) => {
//如果出错(密码错误),在form中展示出错信息
form.setFields([{name: 'password', errors: [message]}]);
});
};
跟踪effect执行情况
通常effect中包含异步操作,对于异步操作我们通常都需要显示Loading,Elux中可以很方便的跟踪它的执行情况,只需要在装饰器effect()
中传入Loading状态Key名即可。
- @effect('this.loginLoading'):表示将执行情况注入
this.state.loginLoading
中 - @effect() 不传参数等于@effect('stage.globalLoading'):表示将执行情况注入
stage.state.globalLoading
中 - @effect(null):参数为null表示不跟踪执行情况
// src/modules/user/model.ts
export class Model extends BaseModel<ModuleState> {
@effect('this.loginLoading') //将该方法的执行情况注入this.state.loginLoading中
public async login(username: string, password: string) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
在View中使用loginLoading
状态
// src/modules/user/views/Login.tsx
export default ({dispatch, loginLoading}) => {
return (
<div>
<button onClick={login} disable={loginLoading==='Start'} >登录</button>
</div>
);
}
自动合并和维护Loading队列
不仅可以很方便的跟踪和注入loading状态,框架还自动维护loading队列,比如相同Key名的多笔loading状态将自动合并成队列管理(队列中的任务全部完成即改变loading状态)。
自动区分浅度Loading和深度Loading
export type LoadingState = 'Start' | 'Stop' | 'Depth';
比如不超过1秒的loading为浅度Loading,否则为深度Loading,这样区分的好处是:对于浅度Loading只需要防止用户重复点击,视觉上用户不用感知,否则会出现一闪而过的Loading界面,反而会影响用户体验。
const Component: FC<Props> = ({loadingState}) => {
return (
<div className="global-loading">
{loadingState === 'Depth' && <div className="loading-icon" />}
</div>
);
};
方便的错误处理
effect执行中出现任何失败或者错误,都将自动派发一个stage._error
的内置action,可以监听它来集中处理错误:
// src/modules/stage/model.ts
export class Model extends BaseModel<ModuleState> {
@effect(null)
protected async ['this._error'](error: CustomError) {
if (error.code === CommonErrorCode.unauthorized) {
this.getRouter().push({url: '/login'}, 'window');
}else{
alert(error.message);
}
throw error;
}
}
支持泛监听
可以使用一个Hander监听多个Action:
- 使用
,
符号分隔多个actionType - 使用
*
符号作为moduleName的通配符 - 使用
this
可以指代本模块名
class Model extends BaseModel
@effect()
//同时监听2个模块的'_initState'
async ['moduleA._initState, moduleA._initState'](){
console.log('moduleA/moduleB inited');
}
@effect()
//同时监听所有模块的'_initState'
async ['*._initState'](){
console.log('all inited');
}
}
还可以路由守卫
Elux中的路由发生跳转时会自动派发几个内置的action:
stage._testRouteChange
:是否允许本次跳转。你可以监听它,阻止路由跳转:export class Model extends BaseModel<ModuleState> {
private checkNeedsLogin(pathname: string): boolean {
return pathname.startsWith('/admin/')
}
@effect(null)
protected async ['this._testRouteChange']({url, pathname}) {
if (!this.state.curUser.hasLogin && this.checkNeedsLogin(pathname)) {
throw new CustomError(CommonErrorCode.unauthorized, '请登录!');
}
}
}
stage._beforeRouteChange
:路由即将跳转。你可以监听它,执行某些逻辑...stage._afterRouteChange
:路由跳转完成。你可以监听它,执行某些逻辑...
多实例历史快照
- 路由push时你可以将当前Store实例冻结起来,并保存在历史栈中。
- 路由back时将自动激活之前被冻结的Store实例,快速恢复历史状态。
自动清理无用状态
传统全局Store有个很大的弊端,就是Store中的状态会不断累积,缺乏自动释放机制。比如当前路由从用户列表
跳转到了文章列表
,如果不主动操作,Store中的userList
可能一直存在。
Elux改进了这个痛点,每次路由发生变化时都将创建一个空的Store,然后挑选出有用的状态重新挂载,这也相当于一种自动垃圾回收机制。
应用
Elux框架奉行轻UI、重Model
的领域驱动理念,推荐将业务逻辑
与UI逻辑
剥离,进行抽象的业务逻辑建模,从而让业务Model可以跨框架、跨平台、跨工程复用。
而其内置的状态管理框架,有效的支撑了这一设计理念,更多信息参见:
最后
好了,感谢小伙伴们耐心看到这里,正如标题所言,如果还是觉得不好,现在可以来打我了,坐标:广西东兴,o友情提醒:泡面不要带少了哦...
[开源]React/Vue通用的状态管理框架,不好用你来打我👀的更多相关文章
- 富文本编辑器TinyMCE的使用(React Vue)
富文本编辑器TinyMCE的使用(React Vue) 一,需求与介绍 1.1,需求 编辑新闻等富有个性化的文本 1.2,介绍 TinyMCE是一款易用.且功能强大的所见即所得的富文本编辑器. Tin ...
- RxJS/Cycle.js 与 React/Vue 相比更适用于什么样的应用场景?
RxJS/Cycle.js 与 React/Vue 相比更适用于什么样的应用场景? RxJS/Cycle.js 与 React/Vue 相比更适用于什么样的应用场景? - 知乎 https://www ...
- Vue+ElementUI的后台管理框架
新开发的一个后台管理系统.在框架上,领导要用AdminLTE这套模板.这个其实很简单,把该引入的样式和js文件引入就可以了.这里就不多赘述了.有兴趣的可以参考:https://www.jianshu. ...
- React / Vue 跨端渲染原理与实现探讨
跨端渲染是渲染层并不局限在浏览器 DOM 和移动端的原生 UI 控件,连静态文件乃至虚拟现实等环境,都可以是你的渲染层.这并不只是个美好的愿景,在今天,除了 React 社区到 .docx / .pd ...
- 在React中跨组件分发状态的三种方法
在React中跨组件分发状态的三种方法 当我问自己第一百次时,我正在研究一个典型的CRUD屏幕:"我应该将状态保留在这个组件中还是将其移动到父组件?". 如果需要对子组件的状态进行 ...
- vue父子组件状态同步的最佳方式续章(v-model篇)
大家好!我是木瓜太香!一名前端工程师,之前写过一篇<vue父子组件状态同步的最佳方式>,这篇文章描述了大多数情况下的父子组件同步的最佳方式,也是被开源中国官方推荐了,在这里表示感谢! 这次 ...
- 关于个人开源项目(vue app)的一些总结
关于个人开源项目(vue app)的一些总结 项目地址 https://github.com/BYChoo/record 项目简介 此项目名叫:Record.是以Vue全家桶(vue,vue-rout ...
- Flutter 状态管理框架 Provider 和 Get 分析
文/ Nayuta,CFUG 社区 状态管理一直是 Flutter 开发中一个火热的话题.谈到状态管理框架,社区也有诸如有以 Get.Provider 为代表的多种方案,它们有各自的优缺点. 面对这么 ...
- 如何利用 React Hooks 管理全局状态
如何利用 React Hooks 管理全局状态 本文写于 2020 年 1 月 6 日 React 社区最火的全局状态管理库必定是 Redux,但是 Redux 本身就是为了大型管理数据而妥协设计的- ...
随机推荐
- Electron学习(三)之简单交互操作
写在前面 最近一直在做批量测试工具的开发,打包的exe,执行也是一个黑乎乎的dos窗口,真的丑死了,总感觉没个界面,体验不好,所以就想尝试写桌面应用程序. 在技术选型时,Java窗体实现使用JavaF ...
- 抓包整理外篇fiddler———— 会话栏与过滤器[二]
前言 简单介绍一下会话栏和过滤器 正文 在抓包的时候这两个可以说是必用吧. 会话栏: 会话栏我这里介绍根据左边部分和右边部分. 左边部分是一些图标,有些人发现有个习惯,不习惯看图标. 其实说白了,我们 ...
- Vite+TS带你搭建一个属于自己的Vue3组件库
theme: nico 前言 随着前端技术的发展,业界涌现出了许多的UI组件库.例如我们熟知的ElementUI,Vant,AntDesign等等.但是作为一个前端开发者,你知道一个UI组件库是如何被 ...
- 腾讯云EKS 上部署 eshopondapr
腾讯云容器服务(Tencent Kubernetes Engine,TKE)基于原生 kubernetes 提供以容器为核心的.高度可扩展的高性能容器管理服务.腾讯云容器服务完全兼容原生 kubern ...
- 利用噪声构建美妙的 CSS 图形
在平时,我非常喜欢利用 CSS 去构建一些有意思的图形. 我们首先来看一个简单的例子.首先,假设我们实现一个 10x10 的格子: 此时,我们可以利用一些随机效果,优化这个图案.譬如,我们给它随机添加 ...
- Python 中的"self"是什么
在使用 pycharm 编写 Python 时,自动补全总会把函数定义的第一个参数定义为 self .遂查,总结如下: self 大体上和静态语言如 Java 中的 this 关键字类似,用于指代实例 ...
- 多线程与高并发(二)—— Synchronized 加锁解锁流程
前言 上篇主要对 Synchronized 的锁实现原理 Monitor 机制进行了介绍,由于 Monitor 基于操作系统调用,上下文切换导致开销大,在竞争不激烈时性能不算很好, 在 jdk6 之后 ...
- Pandas简单操作(学习总结)
Pandas 的主要数据结构是 Series (一维数据)与 DataFrame(二维数据),是一个提供高性能.易于使用的数据结构和数据分析工具. 接下来查看Pandas的基本使用: # 导入模块 i ...
- GP查询表状态常用SQL
- 程序员的专属浪漫——用3D Engine 5分钟实现烟花绽放效果
谁说程序员不懂浪漫? 作为程序员,用自己的代码本事手搓一个技术感十足的惊喜,我觉得,这是不亚于车马慢时代手写信的古典主义浪漫. 那么,应该怎样创作出具有自我身份属性的浪漫惊喜呢? 玩法很多,今天给大家 ...