Skip to content

Commit

Permalink
Merge pull request #42 from dvajs/break-changes
Browse files Browse the repository at this point in the history
Break changes: namespace, reducers, effects and subscriptions
  • Loading branch information
sorrycc authored Aug 22, 2016
2 parents 5af4058 + c304325 commit 01ff888
Show file tree
Hide file tree
Showing 30 changed files with 898 additions and 475 deletions.
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ build:
NODE_ENV=development ./node_modules/.bin/browserify index.js --standalone=dva -u react -u react-dom -g browserify-shim -t envify > dist/dva.js
NODE_ENV=development ./node_modules/.bin/browserify router.js --standalone=dva.router -u react -u react-dom -g browserify-shim -t envify > dist/router.js
NODE_ENV=development ./node_modules/.bin/browserify fetch.js --standalone=dva.fetch -u react -u react-dom -g browserify-shim -t envify > dist/fetch.js
NODE_ENV=development ./node_modules/.bin/browserify effects.js --standalone=dva.effects -u react -u react-dom -g browserify-shim -t envify > dist/effects.js
./node_modules/.bin/uglifyjs -o dist/dva-min.js dist/dva.js
./node_modules/.bin/uglifyjs -o dist/router-min.js dist/router.js
./node_modules/.bin/uglifyjs -o dist/fetch-min.js dist/fetch.js
./node_modules/.bin/uglifyjs -o dist/effects-min.js dist/effects.js

copy:
cp lib/* ./examples/user-dashboard/node_modules/dva/lib/
Expand Down
92 changes: 80 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ Lightweight elm-style framework based on react and redux.
- [subscription 及其适用场景](https://github.com/dvajs/dva/issues/3#issuecomment-229250708)
- [支付宝前端应用架构的发展和选择: 从 roof 到 redux 再到 dva](https://github.com/sorrycc/blog/issues/6)

## Features

- based on redux, redux-saga and react-router
- **small api:** only 5 methods
- **transparent side effects:** using effects and subscriptions brings clarity to IO
- **mobile and react-native support:** don't need router
- **dynamic model and router:** split large scale app on demand
- **plugin system:** with hooks
- **hmr support:** components and routes is ready

## Demos

- [HackerNews](https://dvajs.github.io/dva-hackernews/) ([repo](https://github.com/dvajs/dva-hackernews), [intro](https://github.com/sorrycc/blog/issues/9))
- [Count](./examples/count) ([jsfiddle](https://jsfiddle.net/puftw0ea/))
- [Popular Products](./examples/popular-products)
- [Friend List](./examples/friend-list)
- [User Dashboard](./examples/user-dashboard)

## Getting Started

### Install
Expand All @@ -25,7 +43,7 @@ Lightweight elm-style framework based on react and redux.
$ npm install --save dva
```

### Usage Example
### Example

Let's create an count app that changes when user click the + or - button.

Expand All @@ -42,8 +60,8 @@ app.model({
namespace: 'count',
state: 0,
reducers: {
['count/add' ](count) { return count + 1 },
['count/minus'](count) { return count - 1 },
add (count) { return count + 1 },
minus(count) { return count - 1 },
},
});

Expand All @@ -54,7 +72,7 @@ const App = connect(({ count }) => ({
return (
<div>
<h2>{ props.count }</h2>
<button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
<button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
<button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
</div>
);
Expand All @@ -68,21 +86,71 @@ app.router(({ history }) =>
);

// 5. Start
app.start(document.getElementById('root'));
app.start('#root');
```

## Examples
## API

- [Count](./examples/count) ([jsfiddle](https://jsfiddle.net/puftw0ea/))
- [Popular Products](./examples/popular-products)
- [Friend List](./examples/friend-list)
- [User Dashboard](./examples/user-dashboard)
### `app = dva(opts)`

Initialize a new `dva` app. opts 里除 `history``initialState` 外会被传递给 [app.use](#appusehooks) .

- `opts.history:` default: `hashHistory`
- `opts.initialState:` default: `{}`

`opts.history` 是给路由用的 history,支持 hashHistory 和 browserHistory 。默认 hashHistory,要换成 browserHistory 可以这样:

```javascript
import { browserHistory } from 'dva/router';
const app = dva({
history: browserHistory,
});
```

`opts.initialState` 是给 store 的初始值,优先级高于 model 里的 state 。

### `app.use(hooks)`

dva 的插件机制是通过 hooks 实现的,用于添加自定义行为和监听器。

目前支持以下 hooks :

- `onError(err => {}):` effects 和 subscriptions 出错时触发
- `onAction(Array|Function):` 等同于 redux middleware,支持数组
- `onStateChange(listener):` 绑定 listner,state 变化时触发
- `onReducer(reducerEnhancer):` 应用全局的 reducer enhancer,比如 [redux-undo](https://github.com/omnidan/redux-undo)
- `onHmr(render => {}):` 提供 render 方法用于重新渲染 routes 和 components,暂还不支持 model
- `extraReducers(obj):` 提供额外的 reducers,比如 [redux-form](https://github.com/erikras/redux-form) 需要全局 reducer `form`

### `app.model(obj)`

Create a new model. Takes the following arguments:

- **namespace:** 通过 namespace 访问其他 model 上的属性,不能为空
- **state:** 初始值
- **reducers:** 同步操作,用于更新数据,由 `action` 触发
- **effects:** 异步操作,处理各种业务逻辑,不直接更新数据,由 `action` 触发,可以 dispatch `action`
- **subscriptions:** 异步只读操作,不直接更新数据,可以 dispatch `action`

### `app.router(({ history }) => routes)`

创建路由。使用和 react-router 相同的配置,不做封装,可用 jsx 格式,也可用 javascript object 的格式支持动态路由。

详见:[react-router/docs](https://github.com/reactjs/react-router/tree/master/docs)

### `app.start(selector?)`

Start the application. 如果没有传入 `selector`,则返回 React Element,可用于 SSR,react-native, 国际化等等。

## FAQ

### dva 命名的来历 ?
### Why is it called dva?

dva is a hero from [overwatch](http://ow.blizzard.cn/heroes/dva). She is cute, and `dva` is the shortest one that is available on npm.

### Is it production ready?

dva 是守望先锋 (overwatch) 里的[英雄](http://ow.blizzard.cn/heroes/dva)。我喜欢使用这个角色,拥有强大的机甲,是个坚实的肉盾,并且她是唯一背景是真实的电竞选手,来自韩国。
Yes.

## License

Expand Down
1 change: 0 additions & 1 deletion effects.js

This file was deleted.

4 changes: 2 additions & 2 deletions examples/count-mobile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ app.model({
namespace: 'count',
state: 0,
reducers: {
['count/add' ](count) { return count + 1 },
['count/minus'](count) { return count - 1 },
add (count) { return count + 1 },
minus(count) { return count - 1 },
},
});

Expand Down
20 changes: 6 additions & 14 deletions examples/count-umd/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
const { connect } = dva;
const { Router, Route } = dva.router;
const { put, call } = dva.effects;

const delay = timeout => {
return new Promise(resolve => {
setTimeout(resolve, timeout);
});
};

console.log(dva.fetch);
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));

// 1. Initialize
const app = dva();
Expand All @@ -18,17 +11,16 @@ app.model({
namespace: 'count',
state: 0,
effects: {
['count/add']: function*() {
console.log('count/add');
add: function*(_, { call, put }) {
yield call(delay, 1000);
yield put({
type: 'count/minus',
});
},
},
reducers: {
['count/add' ](count) { return count + 1 },
['count/minus'](count) { return count - 1 },
add (count) { return count + 1 },
minus(count) { return count - 1 },
},
});

Expand All @@ -39,7 +31,7 @@ const App = connect(({ count }) => ({
return (
<div>
<h2>{ props.count }</h2>
<button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
<button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
<button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
</div>
);
Expand All @@ -53,4 +45,4 @@ app.router(({ history }) =>
);

// 5. Start
app.start(document.getElementById('root'));
app.start('#root');
8 changes: 3 additions & 5 deletions examples/count-undo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ app.model({
namespace: 'count',
state: 0,
reducers: {
['count/add' ](count) { return count + 1 },
['count/minus'](count) { return count - 1 },
add (count) { return count + 1 },
minus(count) { return count - 1 },
},
});

Expand All @@ -48,6 +48,4 @@ app.router(({ history }) =>
);

// 5. Start
app.start(document.getElementById('root'), {
history: useRouterHistory(createHashHistory)({ queryKey: false }),
});
app.start('#root');
12 changes: 6 additions & 6 deletions examples/count/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { Router, Route, useRouterHistory } from '../../router';
import { createHashHistory } from 'history';

// 1. Initialize
const app = dva();
const app = dva({
history: useRouterHistory(createHashHistory)({ queryKey: false }),
});

// 2. Model
app.model({
namespace: 'count',
state: 0,
reducers: {
['count/add' ](count) { return count + 1 },
['count/minus'](count) { return count - 1 },
add (count) { return count + 1 },
minus(count) { return count - 1 },
},
});

Expand All @@ -38,6 +40,4 @@ app.router(({ history }) =>
);

// 5. Start
app.start(document.getElementById('root'), {
history: useRouterHistory(createHashHistory)({ queryKey: false }),
});
app.start('#root');
2 changes: 1 addition & 1 deletion examples/dynamic-load/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ const app = dva();
app.router(require('./router'));

// 5. Start
app.start(document.getElementById('root'));
app.start('#root');
2 changes: 1 addition & 1 deletion examples/dynamic-load/models/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {
age: 1,
},
reducers: {
'profile/changeAge'(state, { payload }) {
'changeAge'(state, { payload }) {
return { ...state, age: payload };
},
},
Expand Down
2 changes: 1 addition & 1 deletion examples/friend-list/components/SearchInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class SearchInput extends Component {
render() {
return (
<input
{...this.props}
value={this.props.value || ''}
placeholder={this.props.placeholder}
onChange={this.handleValueChange}
onKeyDown={this.handleEnterKeyDown}
id="search-input"
Expand Down
38 changes: 18 additions & 20 deletions examples/friend-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,55 @@ import './index.html';
import React from 'react';
import dva from '../../src/index';
import { connect } from '../../index';
import { Router, Route, useRouterHistory } from '../../router';
import { put, call } from '../../effects';
import { Router, Route, useRouterHistory, routerRedux } from '../../router';
import fetch from '../../fetch';
import styles from './index.less';
import SearchInput from './components/SearchInput';
import FriendList from './components/FriendList';

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 1. Initialize
const app = dva();

// 2. Model
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
app.model({
namespace: 'friends',
state: {
query: '',
friends: [],
},
subscriptions: [
function(dispatch) {
hashHistory.listen(location => {
if (location.action === 'POP') {
dispatch({
type: 'friends/setQuery',
payload: location.query.q,
});
}
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
dispatch({
type: 'setQuery',
payload: location.query.q,
});
});
}
],
},
},
effects: {
['friends/setQuery']: [function*({ payload }) {
setQuery: [function*({ payload }, { put, call }) {
yield delay(300);
yield call(hashHistory.push, {
yield call(routerRedux.push, {
query: { q: payload || '' },
});
const { success, data } = yield fetch(`/api/search?q=${payload}`)
.then(res => res.json());
if (success) {
yield put({
type: 'friends/setFriends',
type: 'setFriends',
payload: data,
});
}
}, { type: 'takeLatest' }],
},
reducers: {
['friends/setQuery'](state, { payload }) {
setQuery(state, { payload }) {
return { ...state, query: payload };
},
['friends/setFriends'](state, { payload }) {
setFriends(state, { payload }) {
return { ...state, friends: payload };
},
},
Expand Down Expand Up @@ -89,4 +87,4 @@ app.router(({ history }) =>
);

// 5. Start
app.start(document.getElementById('root'));
app.start('#root');
3 changes: 2 additions & 1 deletion examples/popular-products/components/ProductList/Product.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import styles from './Product.less';
import { Icon } from 'antd';
import Icon from 'antd/lib/icon';
import 'antd/lib/icon/style/css.js';

function Product(props) {
const { id, thumb_url, vote, title, description, submitter } = props.data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { Component, PropTypes } from 'react';
import styles from './ProductList.less';
import Product from './Product';
import { Spin } from 'antd';
import Spin from 'antd/lib/spin';
import 'antd/lib/spin/style/css.js';

function ProductList(props) {
return (
Expand Down
Loading

0 comments on commit 01ff888

Please sign in to comment.