<p>dva 介绍</p>

创建于 2016-06-24  ·  76评论  ·  资料来源: 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 等)

最有用的评论

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

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

所有76条评论

被 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.

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

削弱这个!

干得好。谢谢!!

@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
感觉瞬间白痴了,不知道能不能麻烦您给我讲解一下,谢谢了!

为什么是 dva ? 请用英语

我不是很喜欢这种写法。

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

你能用英文启动同一个页面吗? 我们无法理解这一点,以及为什么我们需要 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对于搭建工程很有参考价值。

干得好~

我在哪里可以找到英文文档??? 用翻译引擎翻译主题是有问题的,理解不够。 有了英语,你们就可以走向世界。 保持良好的工作!! :火箭:

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

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

我们有机会获得文档的英文翻译吗?
谢谢!

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

很好
但是当我希望构建一个页面时它会构建所有页面

_非官方翻译_

为什么是德瓦?

Redux 很好。 但是概念太多,分离的reducers、sagas和actions(拆分成不同的文件)

  1. 必须经常在 reducer、sages 和 action 之间切换
  2. 组织业务模型(或领域模型)的不便。 对于exp.,当我们已经有了user_list,并且需要product_list,那么必须复制一个文件副本
  3. 传奇很难写。 你必须为每一个动作制作 fork -> watcher -> worker。
  4. 输入繁琐复杂

什么是 DVA?

它是对现有框架(redux + react-router + redux-saga ...)的精简包装。 不涉及新概念。 < 100 行代码。 (灵感来自 elm 和 choo。)

它是一个框架,而不是一个库。 与 Ember.js 一样,它限制了您编写每个部分的方式。 团队合作更可控。 dva 将除了 react 和 react-dom 之外的所有依赖都封装为 peerDependencies

它的实现尽可能少地引入新语法。 它重用依赖项。 例如,路由器定义与 react-router 的 JSX 完全相同。

核心功能是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 };
    },
  },
});

我们曾经创建sagas/products.js, reducers/products.js actions/products.js并在它们之间切换。

关键:

  • 命名空间: key reducer在其rootReducer对象中的 key
  • 状态: initialStatereducer
  • 订阅: [email protected]的新概念,在 dom 准备好时执行: A Farewell to FRP
  • 效果:更容易鼠尾草
  • 减速机

如何使用

查看示例

路线图

  • devtool热重载
  • 路由器的动态配置
  • Effects支持更多saga模型
  • 单元测试
  • 更多示例:todolist、antd-init 中的用户、热门产品

常问问题

开发工具支持?

兼容 redux-devtool、css livereload。 需要更多的工作来进行热重载

适合产品环境吗?

当然

包括 redux + redux-saga 的所有功能?

是的

浏览器兼容性?

由于 redux-saga 没有 IE8。 (稍后可能会在效果层上应用 thunk、promise、observable 作为扩展)

请问类似

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

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

@clemTheDasher函数名称可以是 JavaScript 中的计算键数组)。 更多详细参考方法定义 |

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这是计算属性。

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

学习!

向大神看齐

感谢大神,感谢开源

我是不是不能向你们学习!

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

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 的作用。

这里不知道是否有支持?

此页面是否有帮助?
0 / 5 - 0 等级