blog icon indicating copy to clipboard operation
blog copied to clipboard

支付宝前端应用架构的发展和选择

Open sorrycc opened this issue 8 years ago • 103 comments

对 Roof 不感兴趣的同学可以直接从 Redux 段落读起。

下文说说我理解的支付宝前端应用架构发展史,从 roof 到 redux,再到 dva

Roof 应该是从 0.4 开始在项目里大范围推广的。

Roof 0.4

Roof 0.4 接触不多,时间久了已经没有太多印象了,记忆中很多概念是从 baobab 里来的,通过 cursor 订阅数据,并基于此设计了很多针对复杂场景的解决方案。

这种方式灵活且强大,现在想想如果这条路一走到底,或许比现在要好一些。但由于概念比较多,当时大家都比较难理解 cursor 这类的概念。并且 redux 越来越流行。。

Roof 0.5

然后有了 Roof 0.5,提供 createRootContainer 和 createContainer,实现类似 react-redux 里 Provider 和 connect 的功能,并隐藏了 cursor 的概念。

// 定义 state
createRootContainer({
  user: { name: 'chris', age: 30 }
})(App);

// 绑定 state
createContainer({
  myUser: 'user',
})(UserInfo);

这在一定程度上迎合了 redux 用户的习惯。但 redux 用户却并不满足,就算不能用 redux,也希望能在 roof 上使用上更多 redux 相关的特性。

还有个在这一阶段讨论较多的另一个问题是没有最佳实践,大家针对同一个问题通常有不同的解法。最典型的是异步请求的处理,有些人直接写从 Component 生命周期里,有些好一点的提取成 service/api,但还是在 Component 里调,还有些提取成 Controller 。

这是 library 相对于 framework 的略势,Roof 本质上是一个 library,要求他去解决所有开发中能想到的问题其实是不公平的。那么如何做的? 目前看起来有两种方案,1) boilerplate 2) framework 。这在之后会继续探讨。

Roof 0.5.5

在经历了几个 bugfix 版本之后,Roof 0.5.5 却是个有新 feature 的更新。感觉从这个版本起已经不是原作者的本意了,而是对于用户的妥协。

这个版本引入了一个新的概念:action

这也是从 redux (或者说 flux) 里而来的,所有用户操作都可以被理解成是一个 action,这样在 Component 里就不用直接调 Controller 或者 api/service 里的接口了,一定程度上做了解耦。

createActionContainer({
  myUser: 'user',
}, {
  // 绑定 actions
  userActions,
})(UserInfo);

这让 Roof 越来越像 redux,但由于没有引入 dispatch,在实际项目中遇到了不少坑。比较典型的是 action 之间的互相调用。

function actionA() {
  actionB();
}
function actionB() {}

还有 action 里更新数据之前必须重新从 state 里拉最新的进行更新之类的问题,记得当时还写过 issue 来记录踩过的坑。这是想引入 redux,但却只引入一半的结果。

Roof 0.5.6@beta

然后是 Roof 0.5.6@beta,这个版本的内核已经换成了 redux,引入 reducerdispatch 来解决上个版本遇到的问题。所以本质上他等同于 react-redux,看下 import 语句应该就能明白。

import { createStore, combineReducers } from 'redux';
import { createDispatchContainer, createRootContainer } from 'roof';

大家可能注意到这个版本有个 @beta,这也是目前 Roof 的最终版本。因为大家意识到既然已经这样了,为啥不用 redux 呢?

Redux

然后就有不少项目开始用 redux,但是 redux 是一个 library,要在团队中使用,就需要有最佳实践。那么最佳实践是什么呢?

理解 Redux

Redux 本身是一个很轻的库,解决 component -> action -> reducer -> state 的单向数据流转问题。

按我理解,他有两个非常突出的特点是:

  1. predictable,可预测性
  2. 可扩展性

可预测性是由于他大量使用 pure function 和 plain object 等概念(reducer 和 action creator 是 pure function,state 和 action 是 plain object),并且 state 是 immutable 的。这对于项目的稳定性会是非常好的保证。

可扩展性则让我们可以通过 middleware 定制 action 的处理,通过 reducer enhancer 扩展 reducer 等等。从而有了丰富的社区扩展和支持,比如异步处理、Form、router 同步、redu/undo、性能问题(selector)、工具支持。

Library 选择

但是那么多的社区扩展,我们应该如何选才能组成我们的最佳实践? 以异步处理为例。(这也是我觉得最重要的一个问题)

用地比较多的通用解决方案有这些:

redux-thunk 是支持函数形式的 action,这样在 action 里就可以 dispatch 其他的 action 了。这是最简单应该也是用地最广的方案吧,对于简单项目应该是够的。

redux-promise 和上面的类似,支持 promise 形式的 action,这样 action 里就可以通过看似同步的方式来组织代码。

但 thunk 和 promise 都有的问题是,他们改变了 action 的含义,使得 action 变得不那么纯粹了。

然后出现的 redux-saga 让我眼前一亮,具体不多说了,可以看他的文档。总之给我的感觉是优雅而强大,通过他可以把所有的业务逻辑都放到 saga 里,这样可以让 reducer, action 和 component 都很纯粹,干他们原本需要干的事情。

所以在异步处理这一环节,我们选择了 redux-saga

最终通过一系列的选择,我们形成了基于 redux 的最佳实践

新的问题

但就像之前所有的 Roof 版本一样,每个时代的应用架构都有自己的问题。Redux 这套虽然已经比较不错,但仍避免不了在项目中暴露自己的问题。

  1. 文件切换问题

    redux 的项目通常要分 reducer, action, saga, component 等等,我们需要在这些文件之间来回切换。并且这些文件通常是分目录存放的:

    + src
      + sagas
        - user.js
      + reducers
        - user.js
      + actions
        - user.js
    

    所以通常我们需要在这三个 user.js 中来回切换。(真实项目中通常还有 services/user.js 等) 不知大家是否有感觉,这样的频繁切换很容易打断编码思路?

  2. saga 创建麻烦

    我们在 saga 里监听一个 action 通常需要这样写:

    function *userCreate() {
      try {
        // Your logic here
      } catch(e) {}
    }
    function *userCreateWatcher() {
      takeEvery('user/create', userCreate);
    }
    function *rootSaga() {
      yield fork(userCreateWatcher);
    }
    

    对于 redux-saga 来说,这样设计可以让实现更灵活,但对于我们的项目而言,大部分场景只需要用到 takeEvery 和 takeLatest 就足够,每个 action 的监听都需要这么写就显得非常冗余。

  3. entry 创建麻烦

    可以看下这个 redux entry 的例子,除了 redux store 的创建,中间件的配置,路由的初始化,Provider 的 store 的绑定,saga 的初始化,还要处理 reducer, component, saga 的 HMR 。这就是真实的项目应用 redux 的例子,看起来比较复杂。

dva

基于上面的这些问题,我们封装了 dva 。dva 是基于 redux 最佳实践 实现的 framework,api 参考了 choo,概念来自于 elm 。详见 dva 简介

并且除了上面这些问题,dva 还能解决 domain model 组织和团队协作的问题。

来看个简单的例子:(这个例子没有异步逻辑,所以并没有包含 effects 和 subscriptions 的使用,感兴趣的可以看 Popular Products 的 Demo)

import React from 'react';
import dva, { connect } from 'dva';
import { Route } from 'dva/router';

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    ['count/add'  ](count) { return count + 1 },
    ['count/minus'](count) { return count - 1 },
  },
});

// 3. View
const App = connect(({ count }) => ({
  count
}))(function(props) {
  return (
    <div>
      <h2>{ props.count }</h2>
      <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
      <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
    </div>
  );
});

// 4. Router
app.router(
  <Route path="/" component={App} />
);

// 5. Start
app.start(document.getElementById('root'));

5 步 4 个接口完成单页应用的编码,不需要配 middleware,不需要初始化 saga runner,不需要 fork, watch saga,不需要创建 store,不需要写 createStore,然后和 Provider 绑定,等等。但却能拥有 redux + redux-saga + ... 的所有功能。

更多 dva 的详解,后面会逐步补充。

最后

从 Roof 到 Redux 再到 dva 一路走来,每个方案都有自己的优点和缺陷,后一个总是为了解决前一个方案的问题而生,感觉上是在逐步变好的过程中,这让我觉得踏实。

另外,感叹坚持走自己的路是件很困难的事情,尤其是积累了一定用户量之后。在害怕失去用户和保留本心之间需要有个权衡和坚守。

sorrycc avatar Jul 02 '16 04:07 sorrycc

另外,感叹坚持走自己的路是件很困难的事情,尤其是积累了一定用户量之后。在害怕失去用户和保留本心之间需要有个权衡和坚守。

👏

jaredleechn avatar Jul 02 '16 04:07 jaredleechn

我来点赞的

soda-x avatar Jul 02 '16 05:07 soda-x

好文点赞

bobodeng avatar Jul 02 '16 07:07 bobodeng

感谢分享

codering avatar Jul 03 '16 00:07 codering

点赞

SMbey0nd avatar Jul 04 '16 02:07 SMbey0nd

dqaria avatar Jul 04 '16 05:07 dqaria

赞啊

ziluo avatar Jul 05 '16 02:07 ziluo

concefly avatar Jul 05 '16 03:07 concefly

厉害

wsw avatar Jul 05 '16 03:07 wsw

这也太imba了!

ghost avatar Jul 07 '16 02:07 ghost

值得学习,一步一步的前进

fengzhu1131 avatar Jul 07 '16 06:07 fengzhu1131

d.v.a都来了,怎么跟redux其他的插件redux-form配合

dont-see-big-shark avatar Jul 11 '16 01:07 dont-see-big-shark

@wee911 目前还不行,正式发布会支持,通过配置额外的 reducers 。详见:https://github.com/sorrycc/dva/issues/7

sorrycc avatar Jul 11 '16 04:07 sorrycc

@sorrycc 不错,支持

dont-see-big-shark avatar Jul 11 '16 05:07 dont-see-big-shark

大神问个问题,Redux中如何共享数据啊?我现在有这么个案例,(列表页1 -> 详情页 -> 列表页2),列表2中的数据把列表1的数据覆盖了,返回的时候列表1就是列表2的数据。这个问题有没有好的办法?

jetango avatar Jul 13 '16 01:07 jetango

@jetango , 我能想到的几种解决方案:

  1. 列表 1 和列表 2 在全局 state 上存不同的 key
  2. 返回列表 1 的时候重新加载一次数据 (不想重复加载,可以在 fetch 层做缓存)

sorrycc avatar Jul 13 '16 02:07 sorrycc

赞👍

jianhuifan avatar Jul 31 '16 14:07 jianhuifan

赞👍

yxqme avatar Aug 30 '16 06:08 yxqme

赞👍

luoh avatar Sep 13 '16 07:09 luoh

玩游戏就是要赢.

RealDeanZhao avatar Sep 15 '16 14:09 RealDeanZhao

同样遇到了类似的问题,dva是改进,很不错

linkgod avatar Sep 15 '16 15:09 linkgod

我是来点赞的~nice share

1kr avatar Sep 16 '16 14:09 1kr

@sorrycc 期待有一天出一个 react-native 的 starter

u0x01 avatar Sep 21 '16 10:09 u0x01

@u0x01 https://github.com/sorrycc/dva-example-react-native

sorrycc avatar Sep 21 '16 10:09 sorrycc

太过于精简了

lxd90 avatar Sep 22 '16 02:09 lxd90

不错!这很D.Va

yipanbo avatar Oct 01 '16 01:10 yipanbo

👍

xuanxiao2013 avatar Oct 03 '16 12:10 xuanxiao2013

D.va : 这也太IMBA了.

(( BOOM ))

evolutionjay avatar Oct 09 '16 03:10 evolutionjay

看名字才进来了解,好东西

879479119 avatar Oct 18 '16 14:10 879479119

牛逼

akliyiping avatar Oct 18 '16 15:10 akliyiping

·~·

bobodeng avatar Oct 21 '16 07:10 bobodeng

给赞

Liyuk avatar Oct 24 '16 06:10 Liyuk

很好,没有新创造语法~

yurizhang avatar Nov 02 '16 09:11 yurizhang

:+1: 我是来点赞的,外行需要这样简化的工具来入门 :P

nilyang avatar Nov 03 '16 14:11 nilyang

看了一下框架 也动手捣鼓了一晚上 发现最终webpack还是全站打一个bundle 请问下dva其实支不支持像ant.design这样子 分页打包 跳页的时候再加载需要的js文件呢?

nerf this

DickyT avatar Nov 08 '16 01:11 DickyT

@DickyT 参考这个例子,https://github.com/dvajs/dva/tree/master/examples/dynamic-load

sorrycc avatar Nov 08 '16 02:11 sorrycc

明白了,谢谢!没想到里面还有一个例子!

chencheng (云谦) [email protected] 於 2016年11月7日星期一 寫道:

@DickyT https://github.com/DickyT 参考这个例子,https://github.com/ dvajs/dva/tree/master/examples/dynamic-load

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sorrycc/blog/issues/6#issuecomment-259028627, or mute the thread https://github.com/notifications/unsubscribe-auth/AEU2JCg5Z_mXNxrrAEPcatiqqhp9vBzUks5q7-AGgaJpZM4JDmZk .

DickyT avatar Nov 08 '16 07:11 DickyT

有2个问题,

  1. 如果一个页面很复杂, 会导致这个 js 文件代码行数猛增. React 可以用组件的方式进行隔离, 但是这个就必须写在一个文件中了.
  2. dispatch 这样直接调用方便吗? 似乎 bindAction 之后直接调用 action 更方便一点. 直接 HardCode 用来用去的很容易出错的.

zackyang000 avatar Nov 09 '16 07:11 zackyang000

@TossShinHwa

  1. 同一个 model 相关的内容本来就是高内聚,放在一起更合理一些
  2. 如果 action 增多以后也可以考虑增加 const 目录

jaredleechn avatar Nov 10 '16 02:11 jaredleechn

Hi @jaredleechn

感谢回复, 对于第一个问题, 这种将 action / sagas / reducer 合并方式, 代码超过 1000 行可能是无法避免的, 这可能给多人开发带来一定的干扰(潜在的合并冲突). 你们这时候是怎么处理的呢?

再想想 SOLID 中的 open / close 原则, 根据这个原则来看, 是否拆分成更多的文件才是更好的解决方案呢?

zackyang000 avatar Nov 10 '16 06:11 zackyang000

@TossShinHwa 首先你得确认 model 的设计没问题。。

benjycui avatar Nov 10 '16 09:11 benjycui

厉害,是时候尝试下dva了

yuzhouisme avatar Nov 14 '16 03:11 yuzhouisme

点赞

Frank1e0927 avatar Nov 18 '16 03:11 Frank1e0927

@TossShinHwa 我对一个 model 的理解是同一块数据的相关操作集合体,是面向数据的一个拆分,一个 model 一个文件在我看来已经是很细的粒度了,而且在这个粒度上应该很少有会导致冲突的并发协作修改

而且 reducer 自身的逻辑通常都比较简单,如果还有疑问的话结合个例子说说具体什么问题?

jaredleechn avatar Nov 18 '16 04:11 jaredleechn

@jaredleechn 也就是说, 在 dva 中, 一个 model 其实对应的只是一个 entity, 而不是一个 module 的概念对吗.

我们这边的话, 一个 model 其实对应的是一个更大的概念.

比如 Blog 系统, Article 这个模块不仅仅包含 title / content 之类的, 也包含下面所有的 Comments. 按我们现在的设计, 其实他们都是在一个模块里面的, 也就是一个 model, 因为他们之间属于包含关系. Comments 独立存在是没有意义的. 但是按你所说, 在 dva 里面似乎他们就属于2个 model 了?

zackyang000 avatar Dec 09 '16 10:12 zackyang000

@TossShinHwa 我也发现了,一个container 容器组件(Route)对应一个model,没办法处理多个,但是一个model可以将多个相关功能集合在一起,只是路径不一样。 比如, user model,'user/login' , 'user/logout', 'user/register', 'user/findpassword', 'user/profile' 等都可以放到一个model中。但是要用不同的路由组件来connect。 但是,这样就会有一个问题,如果一个页面包含了全部这些组件,怎么组合比较好呢? 不知 @sorrycc 如何看?

nilyang avatar Dec 13 '16 08:12 nilyang

@TossShinHwa

看怎样来设计,如果评论需要更高的拓展性,那就是 entity acticle + entity comment + link,这样在前端用两个 model 来操作就会比较方便;但是如果你把 comment 直接存在了 article 里面,就没什么必要拆成两个 model 了,因为在这样的前提下,如你所说,comment 就不再是 entity,也就没有单独更新的场景

我觉得仍然是那句话,面向 state 操作的一个拆分

@nilyang

一个container 容器组件(Route)对应一个model,没办法处理多个

RouteComponent 和 model 没有必然的关系,一个 RC 可以订阅多个 model 对应的 state

jaredleechn avatar Dec 13 '16 12:12 jaredleechn

@sorrycc,你好。我的npm为2.15.9,node为4.6.0,操作系统是win10。遇到点问题,求教下。 我把 https://github.com/dvajs/dva 项目拉下来后,进入examples/popular-products目录,执行npm install后,执行npm start时到一半就报错了,中间会打开C:\Users\admin.anyproxy_certs文件夹。错误内容如下: ERROR in ./index.js Module build failed: ReferenceError: Unknown plugin "add-module-exports" specifi ed in "E:\tools\node_workspace\dva\.babelrc" at 0, attempted to resolve rela tive to "E:\tools\node_workspace\dva" at E:\tools\node_workspace\dva\examples\popular-products\node_modules\atool- build\node_modules\babel-core\lib\transformation\file\options\option-manager.js: 176:17 at Array.map (native) at Function.normalisePlugins (E:\tools\node_workspace\dva\examples\popular-p roducts\node_modules\atool-build\node_modules\babel-core\lib\transformation\file \options\option-manager.js:154:20) at OptionManager.mergeOptions (E:\tools\node_workspace\dva\examples\popular- products\node_modules\atool-build\node_modules\babel-core\lib\transformation\fil e\options\option-manager.js:229:36) at OptionManager.init (E:\tools\node_workspace\dva\examples\popular-products \node_modules\atool-build\node_modules\babel-core\lib\transformation\file\option s\option-manager.js:374:12) at File.initOptions (E:\tools\node_workspace\dva\examples\popular-products\n ode_modules\atool-build\node_modules\babel-core\lib\transformation\file\index.js :216:65) at new File (E:\tools\node_workspace\dva\examples\popular-products\node_modu les\atool-build\node_modules\babel-core\lib\transformation\file\index.js:139:24) at Pipeline.transform (E:\tools\node_workspace\dva\examples\popular-products \node_modules\atool-build\node_modules\babel-core\lib\transformation\pipeline.js :46:16) at transpile (E:\tools\node_workspace\dva\examples\popular-products\node_mod ules\atool-build\node_modules\babel-loader\lib\index.js:38:20) at E:\tools\node_workspace\dva\examples\popular-products\node_modules\atool- build\node_modules\babel-loader\lib\fs-cache.js:78:18 @ multi index

liyswei avatar Dec 22 '16 08:12 liyswei

@liysw 学习 dva 请参考这个最新教程, #18

sorrycc avatar Dec 22 '16 14:12 sorrycc

@liysw 我觉得是墙的问题导致npm install出异常了

DickyT avatar Dec 27 '16 20:12 DickyT

试试 cnpm

sorrycc avatar Dec 27 '16 22:12 sorrycc

确实redux的频繁切换文件太恼人,下个项目准备用dva了

lokewei avatar Jan 04 '17 09:01 lokewei

有没有dva的交流群呢

ghost avatar Jan 13 '17 08:01 ghost

Hao dong xi

c2pig avatar Feb 03 '17 18:02 c2pig

谢谢,你们的贡献,目前正准备用dva构建antd项目

fanerge avatar Mar 02 '17 14:03 fanerge

这太IMBA了!

sc19931112 avatar Mar 08 '17 07:03 sc19931112

dva 爱你哟!

aximario avatar Mar 16 '17 10:03 aximario

每个方案都有自己的优点和缺陷,后一个总是为了解决前一个方案的问题而生,感觉上是在逐步变好的过程中,这让我觉得踏实。 👍!

ritawr avatar Mar 20 '17 06:03 ritawr

如果是使用了dva,但是想用其他的异步框架呢?比如说如果我想使用rxjs 代替saga实现一些复杂的异步逻辑,应该怎么办呢?不能实现么?

blog-lyn avatar Apr 08 '17 11:04 blog-lyn

good job

addedjacky avatar Apr 25 '17 10:04 addedjacky

nerf this!

nick3 avatar May 27 '17 07:05 nick3

好东西,赞!

Zhangzhenguo3352 avatar Jun 30 '17 07:06 Zhangzhenguo3352

点赞

zhangtda avatar Jul 12 '17 02:07 zhangtda

dva这框架没毛病,没研究过redux,感觉上手有点困难,dva的文档能全面点吗,感觉好多地方都不是很清楚

LiboliboLibolibo avatar Sep 07 '17 07:09 LiboliboLibolibo

大神你好,popular-products 地址失效了。

dreamlivemeng avatar Sep 11 '17 02:09 dreamlivemeng

我觉得理解action的本质,结合redux提供的compose函数,就不会有各种纠结了,首先redux底层只支持的FSA,但我们业务场景中除了同步的action之外,各种异步和promise等action都会出现,这时候结合那个中间件的原理,我们最终将我们的自定义action,不管是function(thunk),还是数组,还是其他复杂对象,最后统统通过逻辑转为FSA就成了啊,redux-thunk感觉挺实用。 所以对于:但 thunk 和 promise 都有的问题是,他们改变了 action 的含义,使得 action 变得不那么纯粹了。就有点不认同了,他们最终都是遵守FSA,否则没的玩,只不过我们复杂的业务场景中,不可能全部都是直接FSA提供

fred1218 avatar Sep 27 '17 03:09 fred1218

点赞

AlexBai1991 avatar Oct 10 '17 09:10 AlexBai1991

向大神学习!

Ccheng2729111 avatar Nov 16 '17 03:11 Ccheng2729111

点赞并向大神学习~~

zhaoqinghuan avatar Nov 22 '17 02:11 zhaoqinghuan

纯粹为了解决问题的初学者,标示一定要看懂。向大神学习。

billychou avatar Dec 04 '17 15:12 billychou

D.va 爱你哟

sunwu51 avatar Dec 07 '17 08:12 sunwu51

支付宝前端应用架构的发展和选择--只是状态管理工具的选择。。。。。我以为。。。还是学习了哈哈哈。个人还是比较喜欢自己配置的一套

xyingzhi avatar Jan 10 '18 06:01 xyingzhi

combineReducers那一块处理的很好 用起来很有feel

951759534 avatar Jan 17 '18 12:01 951759534

向大佬学习...

HuangHongRui avatar Jan 18 '18 09:01 HuangHongRui

我觉得发极大可能性地限制了前端开发的自由。。。还有繁琐的步骤,不觉地它的优点“单向数据流。。。”

martinwithyou avatar Mar 12 '18 08:03 martinwithyou

@martinwithyou 我觉得这是对 React、Redux 的硬性的最佳实践吧,不出规范了,直接出 api,会限制开发的自由

wxlworkhard avatar Mar 26 '18 07:03 wxlworkhard

For the confused English speakers (myself included) here's a cleaned up Google translation of the initial comment here. I haven't modified the translation at all, just formatted the words/code I could recognise.


Here's a Students who are not interested in Roof can read directly from the Redux section.

The following talk about my understanding of the development history of Alipay front-end application architecture, from roof to redux, to dva.

Roof should be widely promoted in the project starting from 0.4.

Roof 0.4

Roof 0.4 does not touch much, but it hasn't got much of an impression for a long time. Many concepts in memory are from baobab, subscribe data through cursor, and based on this design a lot of solutions for complex scenes.

This approach is flexible and powerful. Now think about if this path is going to the end, perhaps better than it is now. However, due to the relatively large number of concepts, it was difficult for us to understand the concept of cursor. And redux is becoming more and more popular.

Roof 0.5

Then, with Roof 0.5, createRootContainer and createContainer are provided, which implements functions like react-redux Provider and connect and hides the concept of cursor.

// define the state
createRootContainer({
  user: { name: 'chris', age: 30 }
})(App);

// bind state
createContainer({
  myUser: 'user',
})(UserInfo);

This to a certain extent caters to the habit of redux users. However, redux users are not satisfied, even if they can not use redux, they also want to use more redux-related features on the roof.

Another problem that has been discussed more often at this stage is that there is no best practice, and people usually have different solutions to the same problem. The most typical is the processing of asynchronous requests, and some people write directly from the Component life cycle, some better extracted as service/api, but still in the Component tune, and some extract into Controller.

This is a slight trend of the library relative to the framework. Roof is essentially a library. It is unfair to ask him to solve all the problems that can come up in the development. So how do you do it? Currently there are two options, 1) boilerplate 2) framework. This will continue to be discussed later.

Roof 0.5.5

After experiencing several bugfix releases, Roof 0.5.5 is an update with new features. Feeling from this version is not the intention of the original author, but for the user's compromise.

This version introduces a new concept: action.

This also comes from redux (or flux), all user operations can be understood as an action, so that in the Component you do not need to directly adjust the interface of the Controller or api/service, to some extent to understand Coupling.

createActionContainer({
  myUser: 'user',
}, {
  // Binding actions
  userActions,
})(UserInfo);

This makes Roof more and more like redux, but because there is no dispatch, there are a lot of pits in the actual project. It is typical to call each other between actions.

function actionA() {
  actionB();
}
function actionB() {}

There is also the problem of having to re-launch the latest updates from the state before updating the data in the action. I remember that I wrote an issue to record the pit that I stepped on. This is to introduce redux, but it only introduces half the results.

Roof 0.5.6@beta

Then there is Roof 0.5.6@beta. This version of the kernel has been replaced with redux. reducer and dispatch have been introduced to solve problems encountered in the previous version. So in essence, he is equivalent to react-redux, see import statement should be able to understand.

import { createStore, combineReducers } from 'redux';
import { createDispatchContainer, createRootContainer } from 'roof';

You may have noticed that this version has a @beta, which is the final version of the current Roof. Because everyone realized that since it is already there, why not use redux?

Redux

Then there are many projects that start using redux, but redux is a library. To use it in a team, you need to have best practices. What is the best practice?

Understand Redux

Redux itself is a very light library, solving the one-way data flow problem of component -> action -> reducer -> state.

According to my understanding, he has two very prominent features:

  1. Predictablity
  2. Extensibility

Predictability is due to his heavy use of concepts such as pure function and plain object (reducer and action creator are pure functions, state and action are plain objects), and state is immutable. This is a very good guarantee for the stability of the project.

Extensibility allows us to customize the action processing through middleware, extend reducer with reducer enhancer, and so on. As a result, there is a wealth of community extensions and support, such as asynchronous processing, Form, router synchronization, redu/undo, performance issues, and tool support.

Library selection

But with so many community expansions, how should we choose to form our best practices? Take asynchronous processing as an example. (This is also the most important issue I think)

The more common solution for land use has these:

redux-thunk is an action that supports function forms so that other actions can be dispatched in the action. This is the simplest and most widely used plan. It should be enough for simple projects.

redux-promise is similar to the above, and supports promise-style actions so that actions can organize code in a seemingly synchronous manner.

But the problem with thunks and promises is that they change the meaning of the action and make the action less pure.

Then the emergence of redux-saga made me shine, not much to say, you can see his document. All in all, it gives me a feeling of elegance and power. By using it, he can put all the business logic into saga, which can make the reducer, action and component pure and do what they need to do.

So in the asynchronous processing of this link, we chose redux-saga.

In the end, through a series of choices, we formed a best practice based on redux.

New question

But like all previous versions of Roof, the application architecture of each era has its own problems. Although this set of Redux is quite good, it still can't avoid exposing its own problems in the project.

  1. File switching problem

    redux projects are usually divided into reducer, action, saga, component, etc. We need to switch back and forth between these files. And these files are usually stored in separate directories:

    + src
      + sagas
        - user.js
      + reducers
        - user.js
      + actions
        - user.js
    

    So usually we need to switch back and forth between these three user.js. (Real projects often have services/user.js, etc.) I wonder if everyone has a feeling, this frequent switching is easy to interrupt the coding idea?

  2. Saga create trouble

    Listening to an action in saga usually requires this:

    function *userCreate() {
      try {
        // Your logic here
      } catch(e) {}
    }
    function *userCreateWatcher() {
      takeEvery('user/create', userCreate);
    }
    function *rootSaga() {
      yield fork(userCreateWatcher);
    }
    

    For redux-saga, this design can make the implementation more flexible, but for our project, most of the scenes only need to use takeEvery and takeLatest is enough, each action listener needs to write so it seems very redundant .

  3. entry create trouble

    You can see this redux entry example, in addition to the redux store creation, middleware configuration, routing initialization, Provider store binding, saga initialization, but also to handle reducer, component, saga HMR. This is an example of a real project using redux, which looks complicated.

dva

Based on these issues above, we have encapsulated dva. dva is a framework based on redux best practices. The API references choo and the concept comes from elm. See dva introduction for details.

And in addition to these issues, dva can also solve domain model organization and team collaboration problems.

Let's look at a simple example: (This example does not have asynchronous logic, so it does not include the use of effects and subscriptions. For more information, see Popular Products Demo)

import React from 'react';
import dva, { connect } from 'dva';
import { Route } from 'dva/router';

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    ['count/add' ](count) { return count + 1 },
    ['count/minus'](count) { return count - 1 },
  },
});

// 3. View
const App = connect(({ count }) => ({
  count
}))(function(props) {
  return (
    <div>
      <h2>{ props.count }</h2>
      <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
      <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
    </div>
  );
});

// 4. Router
app.router(
  <Route path="/" component={App} />
);

// 5. Start
app.start(document.getElementById('root'));

5 steps 4 interfaces to complete the application of a single page, do not need to match middleware, do not initialize saga runner, do not need fork, watch saga, do not need to create a store, do not write createStore, and then bind with the Provider, and so on. But it can have all the power of redux + redux-saga + ...

ianchanning avatar Mar 29 '18 22:03 ianchanning

楼主威武,我也想成为像楼主一样的前端大牛,哈哈

yuxingzhu avatar Apr 24 '18 08:04 yuxingzhu

dva将我们从自己组装react-router之类的事情中解放出来了!

laixintao avatar May 02 '18 06:05 laixintao

dva在整合React技术栈确实解放了很多非必要的工作,model里处理reducers的方式很优雅,但在灵活性方面不如自己组装来得活,这可能也是dva倾向以framwork方式,来约定一套最佳实践的规范吧

Haitl avatar May 04 '18 16:05 Haitl

但 thunk 和 promise 都有的问题是,他们改变了 action 的含义,使得 action 变得不那么纯粹了。

然后出现的 redux-saga 让我眼前一亮,具体不多说了,可以看他的文档。总之给我的感觉是优雅而强大,通过他可以把所有的业务逻辑都放到 saga 里,这样可以让 reducer, action 和 component 都很纯粹,干他们原本需要干的事情。

emmm,感谢分享!有一个小疑问,thunk在项目中运用出现的问题仅仅是action语义不明确吗?如果是这样的话似乎不够成为切换新框架的理由。

pansy199211 avatar Jun 03 '18 23:06 pansy199211

嗯,你说的对的

在 2018-06-04 07:38:11,"Panshiyao" [email protected] 写道:

但 thunk 和 promise 都有的问题是,他们改变了 action 的含义,使得 action 变得不那么纯粹了。

然后出现的 redux-saga 让我眼前一亮,具体不多说了,可以看他的文档。总之给我的感觉是优雅而强大,通过他可以把所有的业务逻辑都放到 saga 里,这样可以让 reducer, action 和 component 都很纯粹,干他们原本需要干的事情。

emmm,感谢分享!有一个小疑问,thunk在项目中运用出现的问题仅仅是action语义不明确吗?如果是这样的话似乎不够成为切换新框架的理由。

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

yuxingzhu avatar Jun 04 '18 05:06 yuxingzhu

点个赞。我们项目样板代码写多了以后,自己也封装了一些高阶组件来简化一些代码,比如写了分页组件来基于约定(90%的情况是基于一个 API 和分页方式)配置式地获取列表并渲染到页面上,使得开发一个 feature 从300行样板代码1天工作量减少到15行半小时就搞定。然后这些支撑组件抽取出来,就会遇到更多平时写业务组件很少碰到的问题,比如性能优化、API 设计、权衡定制化需求和约定配置等。有了这层经验,看到 dva 从 redux 最佳实践全家桶的层面来做这层简化,并打磨成为一个产品,觉得是很厉害的。

EthanLin-TWer avatar Jun 27 '18 10:06 EthanLin-TWer

确实是可以的

在 2018-06-27 18:14:49,"Linesh" [email protected] 写道:

点个赞。我们项目样板代码写多了以后,自己也封装了一些高阶组件来简化一些代码,比如写了 paginator 基于约定(90%的情况是基于一个 API 和分页方式)配置式地获取列表并渲染到页面上,使得开发一个 feature 从300行样板代码1天工作量减少到15行半小时就搞定。然后这些支撑组件抽取出来,就会遇到更多平时写业务组件很少碰到的问题,比如性能优化、API 设计、权衡定制化需求和约定配置等。有了这层经验,看到 dva 从 redux 最佳实践全家桶的层面来做这层简化,并打磨成为一个产品,觉得是很厉害的。

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

yuxingzhu avatar Jun 28 '18 02:06 yuxingzhu

@panshiyao 我和他的观点是一样的好奇? 这个例子没有了 Popular Products 的 Demo 有新的链接吗?或者是哪个版本的 example?

yeyuguo avatar Jul 07 '18 14:07 yeyuguo

前沿

shanksgx avatar Aug 21 '18 09:08 shanksgx

最近学dva,感觉说的和我自己考虑的想法的差不多,对于新手来说挺好用的,避免了redux的复杂。ps:现在的技术框架隐藏的细节越来越多了,要不是从最普通的redux开始用起,大概要花很长时间来理解新框架里的原理。

gitHber avatar Oct 09 '18 08:10 gitHber

不得不说,大厂总是走在前沿的,17年我还在研究怎么在项目中加入redux,并且为复杂的写法头痛。18年才开始用dva,省了不少事,对比了同类型框架,同样的功能下,开发过程很友好

Gaven-Xu avatar Oct 16 '18 01:10 Gaven-Xu

我能说我玩守望Dva很强么

Jadeite2 avatar Oct 25 '18 11:10 Jadeite2

After releasing many open source projects at Facebook, we have learned that trying to make everyone happy at the same time produced projects with poor focus that didn’t grow well. Instead, we found that picking a small audience and focusing on making them happy brings a positive net effect. That’s exactly what we did with React, and so far solving the problems encountered by Facebook product teams has translated well to the open source community.

The downside of this approach is that sometimes we fail to give enough focus to the things that Facebook teams don’t have to deal with, such as the “getting started” experience. We are acutely aware of this, and we are thinking of how to improve in a way that would benefit everyone in the community without making the same mistakes we did with open source projects before.

Dan Abramov

Cygra avatar Nov 03 '18 07:11 Cygra

在异步处理上面 当时没有考虑redux-observable吗 为什么没有采用这个呢

nanfs avatar Feb 01 '19 03:02 nanfs

文章质量高

cllemon avatar Mar 09 '19 14:03 cllemon

学习了

xiaobinwu avatar May 28 '19 14:05 xiaobinwu

大牛就是大牛

running-snail-sfs avatar Jul 09 '19 06:07 running-snail-sfs

Do not send useless comments

snowman avatar Aug 15 '19 02:08 snowman

请问能否实现async redux store的方式异步加载reducers?

Felix-Indoing avatar Nov 27 '19 08:11 Felix-Indoing

现在用redux的多还是用dva的多呢?

mvpdream avatar Nov 27 '19 08:11 mvpdream

现在是redux用的比较多

Felix-Indoing avatar Nov 27 '19 08:11 Felix-Indoing

现在是redux用的比较多

为什么不用dva呢? 还有个mbox..

mvpdream avatar Nov 27 '19 08:11 mvpdream

可以看下这个 redux entry 的例子,除了 redux store 的创建,中间件的配置,路由的初始化,Provider 的 store 的绑定,saga 的初始化,还要处理 reducer, component, saga 的 HMR 。这就是真实的项目应用 redux 的例子,看起来比较复杂。 这里的链接失效了,朋友。

brizer avatar Aug 27 '20 05:08 brizer