dva 介绍

Created on 24 Jun 2016  ·  76Comments  ·  Source: dvajs/dva

没有新概念,都是旧的。

Why dva ?

经过一段时间的自学或培训,大家应该都能理解 redux 的概念,并认可这种数据流的控制可以让应用更可控,以及让逻辑更清晰。

但随之而来通常会有这样的疑问:概念太多,并且 reducer, saga, action 都是分离的(分文件)。

这带来的问题是:

  • 编辑成本高,需要在 reducer, saga, action 之间来回切换
  • 不便于组织业务模型 (或者叫 domain model) 。比如我们写了一个 userlist 之后,要写一个 productlist,需要复制很多文件。

还有一些其他的:

  • saga 书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程
  • entry 书写麻烦
  • ...

而 dva 正是用于解决这些问题。

What's dva ?

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )

dva 是 framework,不是 library,类似 emberjs,会很明确地告诉你每个部件应该怎么写,这对于团队而言,会更可控。另外,除了 react 和 react-dom 是 peerDependencies 以外,dva 封装了所有其他依赖。

dva 实现上尽量不创建新语法,而是用依赖库本身的语法,比如 router 的定义还是用 react-router 的 JSX 语法的方式(dynamic config 是性能的考虑层面,之后会支持)。

他最核心的是提供了 app.model 方法,用于把 reducer, initialState, action, saga 封装到一起,比如:

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

在有 dva 之前,我们通常会创建 sagas/products.js, reducers/products.jsactions/products.js,然后在这些文件之间来回切换。

介绍下这些 model 的 key :(假设你已经熟悉了 redux, redux-saga 这一套应用架构)

  • namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值
  • state - 对应 reducer 的 initialState
  • subscription - [email protected] 的新概念,在 dom ready 后执行,这里不展开解释,详见:A Farewell to FRP
  • effects - 对应 saga,并简化了使用
  • reducers

How to use

参考 examples:

Roadmap

  • [x] devtool 热替换支持
  • [x] Router 支持 Dynamic Config
  • [x] Effects 需要支持更多的 saga 模式
  • [ ] Effects 考虑通过扩展的方式接入 thunk, promise, observable 等方案,基本目的是可以兼容 IE8
  • [ ] Component 之间还要传递 dispatch 太麻烦了,考虑下方案
  • [x] 单元测试方案
  • [x] More Examples: todolist, users in antd-init, popular products

    FAQ

开发工具层面的支持?

除了热替换还待适配,其他的比如 redux-devtool, css livereload 等都是兼容的。

是否已经可用于生成环境?

可以。

是否包含之前 redux + redux-saga 那套应用架构的所有功能?

是的。

浏览器兼容性?

IE8 不支持,因为使用了 redux-saga 。(后面会考虑以扩展的方式在 effects 层支持 thunk, promise, observable 等)

Most helpful comment

被 redux 搞得死去活来的,简直是福音啊,太简洁、优雅啦,大赞!!!

btw,今天无意间在推上看到一老外转发了下,以为还是老外写的,没想到是支付宝的同学,👍

All 76 comments

被 redux 搞得死去活来的,简直是福音啊,太简洁、优雅啦,大赞!!!

btw,今天无意间在推上看到一老外转发了下,以为还是老外写的,没想到是支付宝的同学,👍

期待effects的扩展

支付宝生产环境有在使用这套架构么?

@besteric dva 刚出,目前还没来得及应用,但背后的那套应用架构是已经用了有段时间了。

reducer 的写法是不是可以设计成这样:

const reducer = (state, { type, payload }) => {
  switch (type) {
    case 'products/query':
      return { ...state, loading: true, };
    case 'products/query/success':
      return { ...state, loading: false, list: payload };
    default
      return state;
  }
}

app.model({
  reducer
})

这样就可以对 reducer 应用一些高阶方法。

好评,写了几个demo就一个问题,model只能用
app.model(Model1); app.model(Model2);
这样的方法来完成组合吗,其实我觉得理想的是
app.model([Model1,Model2])
之类的

Component 之间还要传递 dispatch 太麻烦了,考虑下方案

不用 bindActionCreators 吗?

@yesmeck reducer 高阶用法的具体场景目前是否也就 redo/undo 了? 我不想 dva 过于灵活,这在之后会考虑通过 addon 的方式加入。

我们项目中用了不少,比如我们会把多个 reducer 逻辑类似的部分抽成一个高阶方法,去修饰原有的 reducer,还有能让 reducer 在路由变化的时候重置状态的高阶方法,还有这个 https://github.com/erikras/multireducer

@Tinker404 感觉分开声明 model 会更清楚,增加删除都比较容易。我会这么写:

app.model(require('../models/a'));
app.model(require('../models/b'));

@JimmyLv 个人倾向于不用 actionCreator,而是直接 dispatch

@yesmeck ok,我再考虑下看。

还有能让 reducer 在路由变化的时候重置状态的高阶方法

感觉这个场景通过在 subscriptions 中订阅路由变化,再通过 action 去重置状态会更合适。或者是用 reducer enhancer 的方法有什么优势吗?

感觉这个场景通过在 subscriptions 中订阅路由变化,再通过 action 去重置状态会更合适

这样的话,需要重置的 reducer 就要每个都写一下重置的逻辑,用高阶方法的话我们现在只要这样:

combineReducers({
  products: composeReducers({  // composeReducers 的实现见下面
    recycle(LOCATION_CHANGE, initialState),  // recycle 用来在路由变化时重置状态
    products
  })
})

还有一个场景就是我说的抽取不同 reducer 的相同逻辑。比如有一个产品列表和一个用户列表,它们的 reducer 是这样的:

// reducers/products.js
const reducer = (state, { type, action}) => {
  switch (type) {
    case 'products/FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        list: payload
      }
    default:
      return state
  }
}
// reducers/users.js
const reducer = (state, { type, payload}) => {
  switch (type) {
    case 'users/FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        list: payload
      }
    default:
      return state
  }
}

这里两个 reducer 几乎是一样的,我们就抽出来写一个 list reducer:

const list = (actionType) => {
  return (state, { type, payload }) => {
    switch (type) {
      case actionType:
        return {
          ...state,
          loading: false,
          list: payload
        }
        break;
      default:
        return state
    }
  }
}

然后我们实现一个composeReducers来组合这3个reducer:

function composeReducers(...reducers) {
  return (state, action) => {
    if (reducers.length === 0) {
      return state
    }

    const last = reducers[reducers.length - 1]
    const rest = reducers.slice(0, -1)

    return rest.reduceRight((enhanced, reducer) => reducer(enhanced, action), last(state, action))
  }
}

这样,产品列表和用户列表的 reducer 就变成这样了:

// reducers/products.js
const reducer = (state, { type, payload}) => {
  // 其他逻辑
}

export default composeReducer(reducer, list('products/FETCH_SUCCESS'))
// reducers/users.js
const reducer = (state, { type, payload}) => {
  // 其他逻辑
}

export default composeReducer(reducer, list('users/FETCH_SUCCESS'))

list 只是一个例子,实际上在项目中是有不少 reducer 会有相同逻辑的。

@yesmeck 👍 ,之前一直低估了 reducer enhancer 的作用。

@sorrycc 能说说为什么吗?用 dispatch 比较显式调用?

@Tinker404 感觉分开声明 model 会更清楚,增加删除都比较容易。我会这么写:
app.model(require('../models/a'));
app.model(require('../models/b'));

我也建议可以一次传入多个model的方法,大型项目可能会有很多model,我现在都是全部require(import)进来,然后一个一个model,其实不太方便,我现在的写法是:

// models是个文件夹,有很多model
import models from './models';

models.forEach((m)=>{
    app.model(m);
});

// models.js
const context = require.context('./', false, /\.js$/);
const keys = context.keys().filter(item => item !== './index.js');
const models = [];
for (let i = 0; i < keys.length; i++) {
  models.push(context(keys[i]));
}
export default models;

这很 D.VA

发现user-dashboard中有antd form组件的使用,我记得是不能用于pure component的,现在可以了吗?

@codering 我没记得有限制, antd 的问题可以到 https://github.com/ant-design/ant-design/issues 提问。

你好,我想用您的这个dva,目前用 React Webpack Redux 脚手架生成的目录结构,参照您example中user-dashboard例子改了代码,可是start之后没有任何内容,您是否可以帮我看看究竟哪里出了问题,我的项目地址:https://github.com/baiyulong/lenovo_parts

@baiyulong 为啥不直接基于 user-dashboard 的目录结构做呢?

@sorrycc 我现在就用user-dashboard的目录结构,请问dva的路由是有特殊处理或者写法么?
export default function({ history }) {
return (
<Router history={history}>
<IndexRoute component={HomePage} />
<Route path='/' component={HomePage}>
<Route path='/create' component={CreateOrder} />
</Route>
</Router>
)
}
我写的这个路由,HomePage可以,写了一个<Link to='/create'>Create</Link>的链接,点击后不能到CreateOrder组件

@baiyulong dva的路由并无特殊写法,你的问题请尝试:

  1. 是否有报错
  2. 尝试直接访问 /create 路由

@nikogu 非常感谢,我把嵌套的拿出来以后就好了

你好,请问dva能不能支持model的热加载?

@kkkf1190 正在考虑这一块,会支持的。

👍

只想说一句谢谢。。。

之前一直觉得vuejs的vue-cli的脚手架非常不错,看了这个,我的想法彻底改变了。

非常精彩的框架!已经研究了一段时间。@sorrycc 两个问题想请教下云大:

  1. dva可完美运用到react native项目中吗?
  2. dva+reactjs是否能很好地支持服务端渲染?

@freemember007

  1. 支持 react-native 的,参考例子:https://github.com/sorrycc/dva-example-react-native
  2. 服务端运行理论上没有问题,背后的 redux 和 react-router 都支持 SSR,但套到 dva 上还要点时间,因为要把相关的逻辑理顺并封装好

@sorrycc 现在对于高阶reducer的支持有方案了吗?我们的项目因为重用,使用了大量的高阶reducer

@ancheel 支持的,可以是全局的或者本地的,参考用例:https://github.com/dvajs/dva/blob/master/test/reducers-test.js

model的state被修改后,如何再次修改,现在老出现这个问题
antd.js:32924 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.

非常精彩,尝试使用于生产环境中,期望能继续优化改进

nerf this!

Good Job。Thanks!!

@sorrycc 期待支持服务端渲染!

@mountainmoon 支持的,参考 https://github.com/sorrycc/dva-boilerplate-isomorphic

一波轮子袭来 :+1:

你好,我刚刚接触学习这个dva,看了几天的代码,心中有几个疑问,想请教一下:
我看了您的demo都是单页面应用,但是开发中都是多页面应用,我想请问,在多页面应开发中如果不使用路由,改如何去加载组件,可能我问的比较白痴,脑子就是有点混乱,因为我没有使用路由,所以在models中设置的监听就不清楚该在哪去触发:
history.listen( location => {
if(location.pathname === '/users') {
dispatch({
type:'querySuccess',
payload:{}
})
}
})
PS: querySuccess 方法中加载数据
而且使用 export default connect(mapStateToProps)(Users); 的时候也报错了:
connect.js:41 Uncaught TypeError: Cannot call a class as a function
感觉瞬间白痴了,不知道能不能麻烦您给我讲解一下,谢谢了!

Why dva ? English please

我不是很喜欢这种写法。

@codering 你提的这个问题 user-dashboard中有antd form组件的使用,我记得是不能用于pure component的,现在可以了吗?
我最经也遇到了,如果是纯函数组件,无法通过props.form.getFieldDecorator得到getFieldDecorator函数,如果采用extends创建组件就可以拿到。
不知道大神有没有解决方案 @sorrycc

Can you please launch the same page in English? We are not able to understand this, and why do we need dva.

您好,请问如果一个大项目的话,他的state也会特别庞大,处理起来会很繁琐,是不是应该拆分成多个model?

@yazhou-zyz 我也出现和你一样的问题:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
想请教一下你是如何解决的呢?

学习

继续学习

dva对于搭建工程很有参考价值。

good job~

Where can I find out the docs in English ??? Translating the topic with translator engines is problematic and the understanding is not sufficient enough. With English, you guys can reach the world. Keep up the good work!! :rocket:

dva在React-native 0.47.X以及React16.0.0的版本并不试用

@vecold 一直都能用,说不能用请帖代码或者报错信息

Is there a chance we can get English translation of the docs?
Thanks!

业务代码中,常见这样的例子,某一局部状态更新,牵一发而动全身,很多地方不需要重新渲染的,也重新渲染,大大降低页面性能。可否添加这个功能,自动分析redux connect依赖的state,减少不必要的mapStateToProps计算和re-render 👍

very good
but it build all page when i hope to build a single page

_unofficial translation_

Why Dva?

Redux is good. But there are too many concepts, separated reducers, sagas and actions (split into different files)

  1. Must switch among reducers, sages and actions frequently
  2. Inconvenience to organize business models (or domain models). For exp., when we already have user_list, and product_list is required, then must duplicate a file copy
  3. Saga is hard to write. You must make fork -> watcher -> worker for every single action.
  4. Entry is tedious and complicated

What's Dva?

It's a lite wrapper over existing framework (redux + react-router + redux-saga ...). No new concept involved. < 100 lines code. ( Inspired by elm and choo. )

It's a framework, not a library. Like Ember.js, it constrains the way you write each part. It's more controllable for teamwork. Dva encapsulates all dependencies except react and react-dom as peerDependencies

Its implementation introduces new syntaxes as less as possible. It reuses the dependencies. For exp., router definition is exactly the same way as react-router's JSX.

The core functionality is app.model. It encapsulates reducer, initialState, action, saga altogether.

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

We used to create sagas/products.js, reducers/products.js actions/products.js and switch among them.

key point:

  • namespace: the key of the reducer in its rootReducer object
  • state: initialState of reducer
  • subscription: the new concept of [email protected], executed when dom is ready: A Farewell to FRP
  • effects: easier sage
  • reducers

How to Use

See examples

Roadmap

  • devtool hot-reload
  • Dynamic Config for Router
  • Effects supports more saga models
  • Unit test
  • More Examples: todolist, users in antd-init, popular products

FAQ

Dev-tool supports?

Compatible with redux-devtool, css livereload. Need more work for hot-reload

Good for prod env?

sure

Including all the functionalities of redux + redux-saga?

yes

Browsers compatibility?

No IE8 due to redux-saga. (Later may apply thunk, promise, observable as extensions on the effects layer)

请问类似

['products/query']: function*() {}
['products/query'](state) {}

是什么语法?数组能用作函数名吗?

@clemTheDasher Function name can be computed key(NOT array) in JavaScript. More detail reference to Method definitions | MDN :)

var obj = {
  property( parameters… ) {},
  *generator( parameters… ) {},
  async property( parameters… ) {},
  async* generator( parameters… ) {},

  // with computed keys:
  [property]( parameters… ) {},
  *[generator]( parameters… ) {},
  async [property]( parameters… ) {},

  // compare getter/setter syntax:
  get property() {},
  set property(value) {}
};

新人报道,到此一游,继续努力学习前端知识

@clemTheDasher That's computed property.

为什么 count https://github.com/dvajs/dva/tree/master/examples/count 这个链接404了呢

学习!

向大神看齐

感谢大神,感谢开源

am I not allowed to learn from you guys!

学习了,谢谢有这么方便的框架供我们使用

github的demo链接都已经失效了。

@sorrycc 现在dva支持服务端渲染吗

reducer 的写法是不是可以设计成这样:

const reducer = (state, { type, payload }) => {
  switch (type) {
    case 'products/query':
      return { ...state, loading: true, };
    case 'products/query/success':
      return { ...state, loading: false, list: payload };
    default
      return state;
  }
}

app.model({
  reducer
})

这样就可以对 reducer 应用一些高阶方法。

Redux流派的写法,简洁,修改状态只需要一行,但是似乎把几行代码通过语法糖写到一起了。但我还是需要用...state去把剩下的状态投递到下一站,否则状态就不全了。换句话说,在reduce阶段,可能是会丢部分状态的,如果写错了。

从某些角度上来讲,Vuex的思路更容易读,也更自然。类似这样写(不完全是哦)。

const mutation = {
  ['products/query'](state) {
    state.loading = true
  },
  ['products/query/success'](state, payload) {
    state.loading = false
    state.list = payload
  }
}

从代码上看,我只关心我(同步)修改哪些状态。Vuex在外面应该还包了一层做了状态下一站投递。可能在投递之前还会做一些防御性检查(猜测),或者植入钩子。

问下我已经dva的官网的例子 页面出不来报错了 是升级了吗

请问类似

['products/query']: function*() {}
['products/query'](state) {}

是什么语法?数组能用作函数名吗?

ES6 允许字面量定义对象时,(表达式)作为对象的属性名,即把表达式放在方括号内。

obj = {
  ['xxname']: 'what ever you defined',
  ['xxyy'](args) {
    ....
  }
}

有个疑问啊,'products/query'这种方式去处理reducers的调用,而且与namespace通过字符串的方式关联,后期如果项目变大,比如上百个方法。如果我的namspace必须要改。要改一百方法了?

@yesmeck 👍 ,之前一直低估了 reducer enhancer 的作用。

这里不知道是否有支持?

Was this page helpful?
0 / 5 - 0 ratings