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

Break changes: namespace, reducers, effects and subscriptions #42

Merged
merged 17 commits into from
Aug 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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