Skip to content
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
5 of 7 tasks
sorrycc opened this issue Jun 24, 2016 · 76 comments
Closed
5 of 7 tasks

dva 介绍 #1

sorrycc opened this issue Jun 24, 2016 · 76 comments

Comments

@sorrycc
Copy link
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 - [email protected] 的新概念,在 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
Copy link

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

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

@codering
Copy link
Contributor

期待effects的扩展

@besteric
Copy link

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

@sorrycc
Copy link
Member Author

sorrycc commented Jun 29, 2016

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

@yesmeck
Copy link
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
Copy link

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

@JimmyLv
Copy link

JimmyLv commented Jul 7, 2016

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

不用 bindActionCreators 吗?

@sorrycc
Copy link
Member Author

sorrycc commented Jul 7, 2016

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

@yesmeck
Copy link
Contributor

yesmeck commented Jul 7, 2016

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

@sorrycc
Copy link
Member Author

sorrycc commented Jul 7, 2016

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

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

@sorrycc
Copy link
Member Author

sorrycc commented Jul 7, 2016

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

@sorrycc
Copy link
Member Author

sorrycc commented Jul 7, 2016

@yesmeck ok,我再考虑下看。

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

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

@yesmeck
Copy link
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
Copy link
Member Author

sorrycc commented Jul 8, 2016

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

@JimmyLv
Copy link

JimmyLv commented Jul 9, 2016

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

@nikogu
Copy link
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
Copy link

这很 D.VA

@codering
Copy link
Contributor

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

@sorrycc
Copy link
Member Author

sorrycc commented Jul 20, 2016

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

@codering
Copy link
Contributor

@baiyulong
Copy link

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

@sorrycc
Copy link
Member Author

sorrycc commented Jul 20, 2016

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

@baiyulong
Copy link

@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
Copy link
Member

nikogu commented Jul 21, 2016

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

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

@baiyulong
Copy link

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

@kkkf1190
Copy link

kkkf1190 commented Aug 3, 2016

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

@sorrycc
Copy link
Member Author

sorrycc commented Aug 3, 2016

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

@AllenFang
Copy link

👍

@basharov
Copy link

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

@jarvislin94
Copy link

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

@leoxoocanada
Copy link

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

@jameschenjav
Copy link

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 [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)

@clemTheDasher
Copy link

请问类似

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

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

@whinc
Copy link

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

@nick-yegw
Copy link

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

@linonetwo
Copy link

@clemTheDasher That's computed property.

@yinqiao
Copy link

yinqiao commented Dec 24, 2017

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

@HuangHongRui
Copy link

学习!

@892399698
Copy link

向大神看齐

@BingoBinBingo
Copy link

感谢大神,感谢开源

sorrycc pushed a commit that referenced this issue Feb 1, 2018
@AZZB
Copy link

AZZB commented Feb 8, 2018

am I not allowed to learn from you guys!

@alfredli2017
Copy link

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

@yxy
Copy link

yxy commented Apr 18, 2018

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

@duuliy
Copy link

duuliy commented May 5, 2018

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

@stanleyxu2005
Copy link

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

@wengyulin
Copy link

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

@jingyinggong
Copy link

请问类似

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

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

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

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

@httol
Copy link

httol commented Dec 14, 2019

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

@evilbs
Copy link

evilbs commented Dec 9, 2020

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

这里不知道是否有支持?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests