没有新概念,都是旧的。
Why dva ?
经过一段时间的自学或培训,大家应该都能理解 redux 的概念,并认可这种数据流的控制可以让应用更可控,以及让逻辑更清晰。
但随之而来通常会有这样的疑问:概念太多,并且 reducer, saga, action 都是分离的(分文件)。
这带来的问题是:
还有一些其他的:
而 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.js
和 actions/products.js
,然后在这些文件之间来回切换。
介绍下这些 model 的 key :(假设你已经熟悉了 redux, redux-saga 这一套应用架构)
参考 examples:
devtool
热替换支持开发工具层面的支持?
除了热替换还待适配,其他的比如 redux-devtool, css livereload 等都是兼容的。
是否已经可用于生成环境?
可以。
是否包含之前 redux + redux-saga 那套应用架构的所有功能?
是的。
浏览器兼容性?
IE8 不支持,因为使用了 redux-saga 。(后面会考虑以扩展的方式在 effects 层支持 thunk, promise, observable 等)
被 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的路由并无特殊写法,你的问题请尝试:
@nikogu 非常感谢,我把嵌套的拿出来以后就好了
你好,请问dva能不能支持model的热加载?
@kkkf1190 正在考虑这一块,会支持的。
👍
只想说一句谢谢。。。
之前一直觉得vuejs的vue-cli的脚手架非常不错,看了这个,我的想法彻底改变了。
非常精彩的框架!已经研究了一段时间。@sorrycc 两个问题想请教下云大:
@freemember007
@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(拆分成不同的文件)
它是对现有框架(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
对象中的 keyinitialState
的reducer
查看示例
devtool
热重载Effects
支持更多saga
模型开发工具支持?
兼容 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 的作用。
这里不知道是否有支持?
最有用的评论
被 redux 搞得死去活来的,简直是福音啊,太简洁、优雅啦,大赞!!!
btw,今天无意间在推上看到一老外转发了下,以为还是老外写的,没想到是支付宝的同学,👍