dva
基于 redux、redux-saga 和 react-router 的轻量级前端框架。(Inspired by elm and choo)
2. API [#](#t12. API)
app = dva(opts) 创建应用,返回 dva 实例
app.use(hooks) 配置 hooks 或者注册插件
app.model(model) 注册 model
app.router(({ history, app }) => RouterConfig) 注册路由表
app.start(selector?) 启动应用。selector 可选
import React from 'react'; import dva, { connect } from 'dva'; // 1. Initialize const app = dva(); // 2. Model app.model({ namespace: 'count', state: 0, reducers: { add (count) { return count + 1 }, minus(count) { return count - 1 }, }, }); // 3. View const App = connect(({ count }) => ({ count }))(function(props) { return ( <div> <h2>{ props.count }</h2> <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button> <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button> </div> ); }); // 4. Router app.router(() => <App />); // 5. Start app.start('#root');
3. Model [#](#t23. Model)
model 是 dva 中最重要的概念
import React,{Fragment} from 'react';
import dva,{connect} from 'dva';
import {Route,Router} from 'dva/router';
const app=dva();
app.model({
namespace: 'todos',
state: {
list: [],
filter:'all'
},
reducers: {
loaded(state,{payload:list}) {
return {
...state,list:list
}
},
added(state,{payload:todo}) {
return {
...state,list: [...state.list,{...todo,id:Date.now(),completed:false}]
}
},
toggled(state,{payload:id}) {
return {
...state,
list: state.list.map(item => {
if (item.id===id) {
item.completed=!item.completed;
}
return item;
})
}
}
},
effects: {
*load({},{put,call}) {
const list=[{id:1,text:'1',completed:false},{id:2,text:'2',completed:false}];
yield put({type:'loaded',payload:list});
},
*add({payload},{put,call}) {
yield put({type:'added',payload});
},
*toggle({payload},{put,call}) {
yield put({type:'toggled',payload});
}
},
subscriptions: {
setup({history,dispatch}) {
history.listen(({pathname}) => {
if (pathname=='/todos') {
dispatch({type:'load'});
}
});
}
}
});
const Home=() => <div>Home</div>
let Todos=({list,filter,toggle,add}) => (
<div>
<input ref={input => this.text=input} />
<button onClick={() => {add(this.text.value);this.text.value = ''}}>+</button>
<ul>
{list.map(item => (
<li>
<input type="checkbox" checked={item.completed} onChange={toggle.bind(null,item.id)}/>
{item.text}
</li>
))}
</ul>
</div>
)
let actions={
add(text) {
return {type:'todos/add',payload:{text}}
},
toggle(id) {
return {type:'todos/toggle',payload:id}
}
}
Todos=connect(
state => ({
list: state.todos.list.filter(todo => {
switch (state.filter) {
case 'completed':
return todo.completed;
case 'uncompleted':
return !todo.completed
default:
return true;
}
}),
filter:state.filter
}),
actions
)(Todos);
app.router(({history,app}) => (
<Router history={history}>
<Fragment>
<Route exact path="/" component={Home} />
<Route path="/todos" component={Todos} />
</Fragment>
</Router>
));
app.start('#root');
- namespace model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串
- state 初始值
- reducers 以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state 的地方。由 action 触发。
- effects 以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由 action 触发,可以触发 action,可以和服务器交互,可以获取全局 state 的数据等等。
- subscriptions 以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
3. 测试鼠标点击速度的的应用 [#](#t33. 测试鼠标点击速度的的应用)
这是一个测试鼠标点击速度的 App,记录 1 秒内用户能最多点几次。顶部的 Highest Record 纪录最高速度;中间的是当前速度,给予即时反馈,让用户更有参与感;下方是供点击的按钮。
3.1 安装dva-cli [#](#t43.1 安装dva-cli)
npm install -g dva-cli
dva -v
dva-h
3.2 创建新应用 [#](#t53.2 创建新应用)
dva new counterApp --demo
cd counterApp
npm start
3.3 定义模型 [#](#t63.3 定义模型)
app.model({
namespace: 'count',
state: {
record: 0,
current: 0
}
});
- namespace 是 model state 在全局 state 所用的 key
- state 是默认状态
- state 里的 record 表示 highest record
- current 表示当前速度
3.4 完成Component [#](#t73.4 完成Component)
CountApp\index.js
import styles from './index.less';
import { connect } from 'dva';
const CountApp = ({ record, current, dispatch }) => {
return (
<div className={styles.normal}>
<div className={styles.record}>最高分:{record}</div>
<div className={styles.current}>当前分:{current}</div>
<div className={styles.button}>
<button onClick={() => dispatch({ type: 'count/add' })}>+</button>
</div>
</div>
)
}
export default connect(state => state.count)(CountApp);
CountApp\index.less
.normal{
width:250px;
border:1px solid #CCC;
box-shadow: 1px 1px 1px 2px #CCC,-1px -1px 1px 2px #CCC;
margin:50px auto;
padding:50px;
.record{
font-size:18px;
color:#CCC;
text-align: left;
border-bottom: 1px solid #CCC;
}
.current{
height:150px;
line-height: 150px;
text-align: center;
}
.button{
text-align: center;
button{
width:100px;
height:50px;
background-color: #CCC;
color:#FFF;
}
}
}
3.5 更新state [#](#t83.5 更新state)
更新 state 是通过 reducers 处理的
reducer 是唯一可以更新 state 的地方,这个唯一性让我们的 App 更具可预测性,所有的数据修改都有据可查。reducer 是 pure function,他接收参数 state 和 action,返回新的 state,通过语句表达即 (state, action) => newState。
请注意,这里的 add 和 minus 两个action,在 count model 的定义中是不需要加 namespace 前缀的,但是在自身模型以外是需要加 model 的 namespace
app.model({ namespace: 'count', state: { record: 0, current: 0 }, reducers: { add(state) { const newCurrent = state.current + 1; return { ...state, record: newCurrent > state.record ? newCurrent : state.record, current: newCurrent } }, minus(state) { return { ...state, current: state.current - 1 }; } } });
3.6 异步处理 [#](#t93.6 异步处理)
dva 通过对 model 增加 effects 属性来处理 side effect(异步任务)
这是基于 redux-saga 实现的,语法为 generator
当用户点 + 按钮,数值加 1 之后,会额外触发一个 side effect,即延迟 1 秒之后数值减1 。
effects: { *add(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'minus' }); } },
call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,详见 redux-saga
3.7 订阅键盘事件 [#](#t103.7 订阅键盘事件)
ubscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
-
subscriptions: { keyboard({ dispatch }) { key('space', () => dispatch({ type: 'add' })); } }
3.8 构建应用 [#](#t113.8 构建应用)
$ npm run build
Gitalking ...
Be the first guy leaving a comment!