title: dva的初識(shí)
date: 2018-05-28 15:02:34
tags: dva
內(nèi)心獨(dú)白
這段時(shí)間以前的同事和我極力安利dva 這個(gè)框架运敢,我之前確實(shí)了解過骗炉,但是心想著遵倦,我會(huì)redux,看到一個(gè)技術(shù)文章介紹是什么極簡(jiǎn)的redux實(shí)現(xiàn)方式的標(biāo)題束亏,我就沒想深入去了解签则,何況,本著會(huì)react,redux,react-router,ant-design吃天下的原則拇泣。我就沒去深入了解噪叙,但是慢慢的接觸,我發(fā)現(xiàn)dva的框架做的事情真的很多霉翔,而且在很多方式簡(jiǎn)化了編程睁蕾。今天就了解第一步咱們的dva。
dva 是什么
詳細(xì)的介紹請(qǐng)看這里
這個(gè)其實(shí)是支付寶整合的一套框架早龟,集成了 (redux + react-router + redux-saga 等)的一層輕量封裝惫霸。dva 是 framework猫缭,不是 library,類似 emberjs壹店。
他最核心的是提供了 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 };
},
},
});
現(xiàn)在來介紹一下這些key:
(假設(shè)你已經(jīng)熟悉了 redux, redux-saga 這一套應(yīng)用架構(gòu))
- namespace - 對(duì)應(yīng) reducer 在 combine 到 rootReducer 時(shí)的 key 值
- state - 對(duì)應(yīng) reducer 的 initialState
- subscription - elm@0.17 的新概念硅卢,在 dom ready 后執(zhí)行射窒,這里不展開解釋
- effects - 對(duì)應(yīng) saga,并簡(jiǎn)化了使用
- reducers - 相當(dāng)于數(shù)據(jù)模型
安裝一個(gè)腳手架吧
dva-cli github的地址在這里:https://github.com/dvajs/dva-cli 将塑。
npm install dva-cli -g
dva new myapp && cd myapp
npm start
如果一切進(jìn)行的順利脉顿,那么基本上應(yīng)該是進(jìn)入到這個(gè)頁面
完成幾個(gè)demo ?
參考鏈接 - https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/getting-started.md
這里會(huì)教我們啟動(dòng)和開始一個(gè)demo 点寥,從而熟悉整個(gè)項(xiàng)目
如何去寫艾疟?
接到需求之后推薦的做法不是立刻編碼,而是先以上帝模式做整體設(shè)計(jì)敢辩。
- 先設(shè)計(jì) model
- 再設(shè)計(jì) component
- 最后連接 model 和 component
首先我們要先定義model
app.model({
namespace: 'count',
state: {
record : 0,
current: 0,
},
});
namespace 是 model state 在全局 state 所用的 key蔽莱,state 是默認(rèn)數(shù)據(jù)。然后 state 里的 record 表示 highest record戚长,current 表示當(dāng)前速度盗冷。
其次設(shè)計(jì)component
完成 Model 之后,我們來編寫 Component 同廉。推薦盡量通過 stateless functions 的方式組織 Component仪糖,在 dva 的架構(gòu)里我們基本上不需要用到 state 。
import styles from './index.less';
const CountApp = ({count, dispatch}) => {
return (
<div className={styles.normal}>
<div className={styles.record}>Highest Record: {count.record}</div>
<div className={styles.current}>{count.current}</div>
<div className={styles.button}>
<button onClick={() => { dispatch({type: 'count/add'}); }}>+</button>
</div>
</div>
);
};
- 這里先 import styles from './index.less';迫肖,再通過 styles.xxx 的方式聲明 css classname 是基于 css-modules 的方式锅劝,后面的樣式部分會(huì)用上
- 通過 props 傳入兩個(gè)值,count 和 dispatch咒程,count 對(duì)應(yīng) model 上的 state鸠天,在后面 connect 的時(shí)候綁定,dispatch 用于分發(fā) action
- dispatch({type: 'count/add'}) 表示分發(fā)了一個(gè) {type: 'count/add'} 的 action
更新state
更新 state 是通過 reducers 處理的帐姻。
reducer 是唯一可以更新 state 的地方,這個(gè)唯一性讓我們的 App 更具可預(yù)測(cè)性奶段,所有的數(shù)據(jù)修改都有據(jù)可查饥瓷。reducer 是 pure function,他接收參數(shù) state 和 action痹籍,返回新的 state呢铆,通過語句表達(dá)即 (state, action) => newState。
這個(gè)需求里蹲缠,我們需要定義兩個(gè) reducer棺克,add 和 minus悠垛,分別用于計(jì)數(shù)的增和減。值得注意的是 add 時(shí) record 的邏輯娜谊,他只在有更高的記錄時(shí)才會(huì)被記錄确买。
請(qǐng)注意,這里的 add 和 minus 兩個(gè)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};
+ },
+ },
});
綁定數(shù)據(jù)
在定義了 Model 和 Component 之后,我們需要把他們連接起來派草。這樣 Component 里就能使用 Model 里定義的數(shù)據(jù)搀缠,而 Model 中也能接收到 Component 里 dispatch 的 action 。
這個(gè)需求里只要用到 count近迁。
function mapStateToProps(state) {
return { count: state.count };
}
const HomePage = connect(mapStateToProps)(CountApp);
這里的 connect 來自 react-redux艺普。
路由
接收到 url 之后決定渲染哪些 Component,這是由路由決定的鉴竭。
這個(gè)需求只有一個(gè)頁面歧譬,路由的部分不需要修改。
app.router(({history}) =>
<Router history={history}>
<Route path="/" component={HomePage} />
</Router>
);
樣式
默認(rèn)是通過 css modules 的方式來定義樣式拓瞪,這和普通的樣式寫法并沒有太大區(qū)別缴罗,由于之前已經(jīng)在 Component 里 hook 了 className,這里只需要在 index.less 里填入以下內(nèi)容:
.normal {
width: 200px;
margin: 100px auto;
padding: 20px;
border: 1px solid #ccc;
box-shadow: 0 0 20px #ccc;
}
.record {
border-bottom: 1px solid #ccc;
padding-bottom: 8px;
color: #ccc;
}
.current {
text-align: center;
font-size: 40px;
padding: 40px 0;
}
.button {
text-align: center;
button {
width: 100px;
height: 40px;
background: #aaa;
color: #fff;
}
}
異步處理
在此之前祭埂,我們所有的操作處理都是同步的面氓,用戶點(diǎn)擊 + 按鈕,數(shù)值加 1
現(xiàn)在我們要開始處理異步任務(wù)蛆橡,dva 通過對(duì) model 增加 effects 屬性來處理 side effect(異步任務(wù))舌界,這是基于 redux-saga 實(shí)現(xiàn)的,語法為 generator
在這個(gè)需求里泰演,當(dāng)用戶點(diǎn) + 按鈕呻拌,數(shù)值加 1 之后,會(huì)額外觸發(fā)一個(gè) side effect睦焕,即延遲 1 秒之后數(shù)值 1 藐握。
app.model({
namespace: 'count',
+ effects: {
+ *add(action, { call, put }) {
+ yield call(delay, 1000);
+ yield put({ type: 'minus' });
+ },
+ },
...
+function delay(timeout){
+ return new Promise(resolve => {
+ setTimeout(resolve, timeout);
+ });
+}
- add() {} 等同于 add: function(){}
- call 和 put 都是 redux-saga 的 effects,call 表示調(diào)用異步函數(shù)垃喊,put 表示 dispatch action猾普,其他的還有 select, take, fork, cancel 等,詳見 redux-saga 文檔
- 默認(rèn)的 effect 觸發(fā)規(guī)則是每次都觸發(fā)(takeEvery)本谜,還可以選擇 takeLatest初家,或者完全自定義 take 規(guī)則
訂閱鍵盤事件
在實(shí)現(xiàn)了鼠標(biāo)測(cè)速之后,怎么實(shí)現(xiàn)鍵盤測(cè)速呢?
在 dva 里有個(gè)叫 subscriptions 的概念,他來自于 elm溜在。
Subscription 語義是訂閱陌知,用于訂閱一個(gè)數(shù)據(jù)源,然后根據(jù)條件 dispatch 需要的 action掖肋。數(shù)據(jù)源可以是當(dāng)前的時(shí)間仆葡、服務(wù)器的 websocket 連接、keyboard 輸入培遵、geolocation 變化浙芙、history 路由變化等等。
dva 中的 subscriptions 是和 model 綁定的籽腕。
+import key from 'keymaster';
...
app.model({
namespace: 'count',
+ subscriptions: {
+ keyboardWatcher({ dispatch }) {
+ key('?+up, ctrl+up', () => { dispatch({type:'add'}) });
+ },
+ },
});
這里我們不需要手動(dòng)安裝 keymaster 依賴嗡呼,在我們敲入 import key from 'keymaster'; 并保存的時(shí)候,dva-cli 會(huì)為我們安裝 keymaster 依賴并保存到 package.json 中
構(gòu)建應(yīng)用
我們已在開發(fā)環(huán)境下進(jìn)行了驗(yàn)證皇耗,現(xiàn)在需要部署給用戶使用南窗。敲入以下命令:
$ npm run build
輸出:
> @ build /private/tmp/dva-quickstart
> atool-build
Child
Time: 6891ms
Asset Size Chunks Chunk Names
common.js 1.18 kB 0 [emitted] common
index.js 281 kB 1, 0 [emitted] index
index.css 353 bytes 1, 0 [emitted] index
該命令成功執(zhí)行后,編譯產(chǎn)物就在 dist 目錄下郎楼。
應(yīng)用源碼
應(yīng)用源碼:github地址