共计 3613 个字符,预计需要花费 10 分钟才能阅读完成。
你是原生 Redux 用户?有没有觉得写 Redux 太繁琐了?
你是 dvaJS 用户?有没有觉得 redux-saga 概念太多,且 yield 无法返回 TS 类型?
试试 react-coat 吧:
项目地址:https://github.com/wooline/react-coat
// 仅需一个类,搞定 action、reducer、effect、loading
class ModuleHandlers extends BaseModuleHandlers {
@reducer
protected putCurUser(curUser: CurUser): State {
return {…this.state, curUser};
}
@reducer
public putShowLoginPop(showLoginPop: boolean): State {
return {…this.state, showLoginPop};
}
@effect(“login”) // 使用自定义 loading 状态
public async login(payload: {username: string; password: string}) {
const loginResult = await sessionService.api.login(payload);
if (!loginResult.error) {
this.updateState({curUser: loginResult.data});
Toast.success(“ 欢迎您回来!”);
} else {
alert(loginResult.error.message);
}
}
// uncatched 错误会触发 @@framework/ERROR,监听并发送给后台
@effect(null) // 不需要 loading,设置为 null
protected async [“@@framework/ERROR”](error: CustomError) {
if (error.code === “401”) {
this.dispatch(this.actions.putShowLoginPop(true));
} else if (error.code === “301” || error.code === “302”) {
this.dispatch(this.routerActions.replace(error.detail));
} else {
Toast.fail(error.message);
await settingsService.api.reportError(error);
}
}
// 监听自已的 INIT Action,做一些异步数据请求
@effect()
protected async [“app/INIT”]() {
const [projectConfig, curUser] = await Promise.all([
settingsService.api.getSettings(),
sessionService.api.getCurUser()
]);
this.updateState({
projectConfig,
curUser,
});
}
}
react-coat 特点
集成 react、redux、react-router、history 等相关框架
仅为以上框架的糖衣外套,不改变其基本概念,无强侵入与破坏性
结构化前端工程、业务模块化,支持按需加载
同时支持 SPA(单页应用) 和 SSR(服务器渲染)
使用 typescript 严格类型,更好的静态检查与智能提示
开源微框架,源码不到千行,几乎不用学习即可上手
与 Dva 的异同
引入 ActionHandler 观察者模式,更优雅的处理模块之间的协作
去除 redux-saga,使用 async、await 替代,简化代码的同时对 TS 类型支持更全面
原生使用 typescript 组织和开发,更全面的类型安全
路由组件化、无 Page 概念、更自然的 API 和更简单的组织结构
更大的灵活性和自由度,不强封装脚手架等
支持 SPA(单页应用) 和 SSR(服务器渲染) 快速切换,
支持模块异步按需加载和同步加载快速切换
差异示例:使用强类型组织所有 reducer 和 effect
// Dva 中常这样写
dispatch({type: ‘moduleA/query’, payload:{username:”jimmy”}} })
// 本框架中可直接利用 ts 类型反射和检查:
this.dispatch(moduleA.actions.query({username:”jimmy”}))
差异示例:State 和 Actions 支持继承
// Dva 不支持继承
// 本框架可以直接继承
class ModuleHandlers extends ArticleHandlers<State, PhotoResource> {
constructor() {
super({}, {api});
}
@effect()
protected async parseRouter() {
const result = await super.parseRouter();
this.dispatch(this.actions.putRouteData({showComment: true}));
return result;
}
@effect()
protected async [ModuleNames.photos + “/INIT”]() {
await super.onInit();
}
}
差异示例:在 Dva 中,因为使用 redux-saga,假设在一个 effect 中使用 yield put 派发一个 action,以此来调用另一个 effect,虽然 yield 可以等待 action 的派发,但并不能等待后续 effect 的处理:
// 在 Dva 中,updateState 并不会等待 otherModule/query 的 effect 处理完毕了才执行
effects: {
* query (){
yield put({type: ‘otherModule/query’,payload:1});
yield put({type: ‘updateState’, payload: 2});
}
}
// 在本框架中, 可使用 awiat 关键字,updateState 会等待 otherModule/query 的 effect 处理完毕了才执行
class ModuleHandlers {
async query (){
await this.dispatch(otherModule.actions.query(1));
this.dispatch(thisModule.actions.updateState(2));
}
}
差异示例:如果 ModuleA 进行某项操作成功之后,ModuleB 或 ModuleC 都需要 update 自已的 State,由于缺少 action 的观察者模式,所以只能将 ModuleB 或 ModuleC 的刷新动作写死在 ModuleA 中:
// 在 Dva 中需要主动 Put 调用 ModuleB 或 ModuleC 的 Action
effects: {
* update (){
…
if(callbackModuleName===”ModuleB”){
yield put({type: ‘ModuleB/update’,payload:1});
}else if(callbackModuleName===”ModuleC”){
yield put({type: ‘ModuleC/update’,payload:1});
}
}
}
// 在本框架中, 可使用 ActionHandler 观察者模式:
class ModuleB {
// 在 ModuleB 中兼听 ”ModuleA/update” action
async [“ModuleA/update”] (){
….
}
}
class ModuleC {
// 在 ModuleC 中兼听 ”ModuleA/update” action
async [“ModuleA/update”] (){
….
}
}
遵循规则:
M 和 V 之间使用单向数据流
整站保持单个 Store
Store 为 Immutability 不可变数据
改变 Store 数据,必须通过 Reducer
调用 Reducer 必须通过显式的 dispatch Action
Reducer 必须为 pure function 纯函数
有副作用的行为,全部放到 Effect 函数中
每个 reducer 只能修改 Store 下的某个节点,但可以读取所有节点
路由组件化,不使用集中式配置
快速上手及 Demo
本框架上手简单
8 个新概念:
Effect、ActionHandler、Module、ModuleState、RootState、Model、View、Component
4 步创建:
exportModel(), exportView(), exportModule(), createApp()
3 个 Demo,循序渐进:
入手:Helloworld 进阶:SPA(单页应用)
升级:SPA(单页应用)+SSR(服务器渲染)