New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dva 介绍 #1

Closed
sorrycc opened this Issue Jun 24, 2016 · 72 comments

Comments

Projects
None yet
@sorrycc
Member

sorrycc commented Jun 24, 2016

没有新概念,都是旧的。

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 - elm@0.17 的新概念,在 dom ready 后执行,这里不展开解释,详见:A Farewell to FRP
  • effects - 对应 saga,并简化了使用
  • reducers

How to use

参考 examples:

Roadmap

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

FAQ

开发工具层面的支持?

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

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

可以。

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

是的。

浏览器兼容性?

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

@ystarlongzi

This comment has been minimized.

ystarlongzi commented Jun 24, 2016

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

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

@codering

This comment has been minimized.

Contributor

codering commented Jun 25, 2016

期待effects的扩展

@besteric

This comment has been minimized.

besteric commented Jun 29, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jun 29, 2016

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

@yesmeck

This comment has been minimized.

Contributor

yesmeck commented Jul 6, 2016

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 应用一些高阶方法。

@Tinker404

This comment has been minimized.

Tinker404 commented Jul 7, 2016

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

@JimmyLv

This comment has been minimized.

JimmyLv commented Jul 7, 2016

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

不用 bindActionCreators 吗?

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 7, 2016

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

@yesmeck

This comment has been minimized.

Contributor

yesmeck commented Jul 7, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 7, 2016

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

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

This comment has been minimized.

Member

sorrycc commented Jul 7, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 7, 2016

@yesmeck ok,我再考虑下看。

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

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

@yesmeck

This comment has been minimized.

Contributor

yesmeck commented Jul 7, 2016

感觉这个场景通过在 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 会有相同逻辑的。

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 8, 2016

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

@JimmyLv

This comment has been minimized.

JimmyLv commented Jul 9, 2016

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

@nikogu

This comment has been minimized.

Member

nikogu commented Jul 13, 2016

@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;

@XadillaX

This comment has been minimized.

XadillaX commented Jul 13, 2016

这很 D.VA

@codering

This comment has been minimized.

Contributor

codering commented Jul 16, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 20, 2016

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

@codering

This comment has been minimized.

Contributor

codering commented Jul 20, 2016

@baiyulong

This comment has been minimized.

baiyulong commented Jul 20, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Jul 20, 2016

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

@baiyulong

This comment has been minimized.

baiyulong commented Jul 21, 2016

@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组件

@nikogu

This comment has been minimized.

Member

nikogu commented Jul 21, 2016

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

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

This comment has been minimized.

baiyulong commented Jul 21, 2016

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

@kkkf1190

This comment has been minimized.

kkkf1190 commented Aug 3, 2016

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

@sorrycc

This comment has been minimized.

Member

sorrycc commented Aug 3, 2016

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

@AllenFang

This comment has been minimized.

AllenFang commented Aug 3, 2016

👍

@anchoretics

This comment has been minimized.

anchoretics commented Jun 1, 2017

学习

@zfxj

This comment has been minimized.

zfxj commented Jun 7, 2017

继续学习

@Pines-Cheng

This comment has been minimized.

Pines-Cheng commented Jun 15, 2017

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

@lixuecn lixuecn referenced this issue Jul 23, 2017

Closed

123123 #1

@WangBiaoxuan

This comment has been minimized.

WangBiaoxuan commented Aug 3, 2017

good job~

@aislanmaia

This comment has been minimized.

aislanmaia commented Aug 4, 2017

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!! 🚀

@vecold

This comment has been minimized.

vecold commented Aug 9, 2017

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

@nihgwu

This comment has been minimized.

Member

nihgwu commented Aug 9, 2017

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

@basharov

This comment has been minimized.

basharov commented Aug 21, 2017

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

@JackZong

This comment has been minimized.

JackZong commented Sep 6, 2017

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

@gafish

This comment has been minimized.

gafish commented Sep 27, 2017

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

@jameschenjav

This comment has been minimized.

jameschenjav commented Oct 5, 2017

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 Elm@0.17, 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)

@clemTheDasher

This comment has been minimized.

clemTheDasher commented Dec 16, 2017

请问类似

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

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

@whinc

This comment has been minimized.

whinc commented Dec 19, 2017

@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) {}
};
@nickle-ye

This comment has been minimized.

nickle-ye commented Dec 20, 2017

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

@linonetwo

This comment has been minimized.

linonetwo commented Dec 23, 2017

@clemTheDasher That's computed property.

@yinqiao

This comment has been minimized.

yinqiao commented Dec 24, 2017

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

@HuangHongRui

This comment has been minimized.

HuangHongRui commented Jan 18, 2018

学习!

@892399698

This comment has been minimized.

892399698 commented Jan 24, 2018

向大神看齐

@BingoBinBingo

This comment has been minimized.

BingoBinBingo commented Jan 26, 2018

感谢大神,感谢开源

sorrycc pushed a commit that referenced this issue Feb 1, 2018

@AZZB

This comment has been minimized.

AZZB commented Feb 8, 2018

am I not allowed to learn from you guys!

@alfredli2017

This comment has been minimized.

alfredli2017 commented Mar 19, 2018

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

@yxy

This comment has been minimized.

yxy commented Apr 18, 2018

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

@duuliy

This comment has been minimized.

duuliy commented May 5, 2018

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

@zchen9 zchen9 referenced this issue May 6, 2018

Open

dva 了解下 #13

@linesh-simplicity linesh-simplicity referenced this issue Jun 24, 2018

Closed

【博客2.0】技术栈选型 #208

6 of 21 tasks complete
@stanleyxu2005

This comment has been minimized.

stanleyxu2005 commented Nov 4, 2018

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在外面应该还包了一层做了状态下一站投递。可能在投递之前还会做一些防御性检查(猜测),或者植入钩子。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment