为了防止被打,有请“燕双鹰”镇楼?️♀️?️?️...o...
话说新冠3年,“状态管理框架”豪杰并起、群雄逐鹿,ReduxToolkit、Mobx、Vuex、Pinia、Dva、Rematch、Recoil、Zustand、Mirror...敢问英雄独钟哪厢?
笔者也用过很多态管理框架,大部分都是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个概念:
既然都是玩这3个概念,大家都容易理解,那么要自荐的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});
}
}
所以从糖衣语法来说Elux其实与Dva/Vuex/Pinia也差不多,不同在于:
Decorator装饰器语法来定义reducer(mutation)和effect(action),这样更简洁。Class来组织Model,有2点好处:
除了糖衣语法,Elux还有其更深层次的创新:
从图中可以看出:
这点类似于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中:
class Model{
@reducer
setUser(curUser) {
//vue中可以直接修改state:
this.state.curUser = curUser;
//react中必需返回一个新state
//return {...this.state, curUser};
}
}
当然,在MutableData模式下,返回一个新数据也是可以的,这为跨React和Vue项目共享Model提供了解决方案。
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中包含异步操作,对于异步操作我们通常都需要显示Loading,Elux中可以很方便的跟踪它的执行情况,只需要在装饰器effect()中传入Loading状态Key名即可。
this.state.loginLoading中stage.state.globalLoading中// 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队列,比如相同Key名的多笔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:路由跳转完成。你可以监听它,执行某些逻辑...传统全局Store有个很大的弊端,就是Store中的状态会不断累积,缺乏自动释放机制。比如当前路由从用户列表跳转到了文章列表,如果不主动操作,Store中的userList可能一直存在。
Elux改进了这个痛点,每次路由发生变化时都将创建一个空的Store,然后挑选出有用的状态重新挂载,这也相当于一种自动垃圾回收机制。
Elux框架奉行轻UI、重Model的领域驱动理念,推荐将业务逻辑与UI逻辑剥离,进行抽象的业务逻辑建模,从而让业务Model可以跨框架、跨平台、跨工程复用。
而其内置的状态管理框架,有效的支撑了这一设计理念,更多信息参见:
好了,感谢小伙伴们耐心看到这里,正如标题所言,如果还是觉得不好,现在可以来打我了?,坐标:广西东兴,o友情提醒:泡面不要带少了哦...
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
我想为我的Task模型创建一个status属性,该属性将按以下顺序指示它在三部分进度中的位置:打开=>进行中=>完成。它的工作方式类似于亚马逊包裹的交付方式:已订购=>已发货=>已交付。我想知道设置此属性的最佳方法是什么。我可能是错的,但创建三个独立的bool属性似乎有点多余。实现此目标的最佳方法是什么? 最佳答案 Rails4有一个内置的enummacro.它使用单个整数列并映射到键列表。classOrderenumstatus:[:ordered,:shipped,:delivered]end状态映射如下:{ordered:0,
s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成
我想从rubyrake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调
我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho