因爲以前都用react+redux。此次接觸到dva框架簡單學習下。 本篇文章主要講解了dva框架中開發經常使用API和一些使用技巧,若是想查看更多更全面的API,請參照dva官方文檔: 官方democss
$ npm install -g dva-cli
複製代碼
$ dva new mydva
複製代碼
$ cd myapp
$ npm start
複製代碼
├── mock // mock數據文件夾
├── node_modules // 第三方的依賴
├── public // 存放公共public文件的文件夾
├── src // 最重要的文件夾,編寫代碼都在這個文件夾下
│ ├── assets // 能夠放圖片等公共資源
│ ├── components // 就是react中的木偶組件
│ ├── models // dva最重要的文件夾,全部的數據交互及邏輯都寫在這裏
│ ├── routes // 就是react中的智能組件,不要被文件夾名字誤導。
│ ├── services // 放請求借口方法的文件夾
│ ├── utils // 本身的工具方法能夠放在這邊
│ ├── index.css // 入口文件樣式
│ ├── index.ejs // ejs模板引擎
│ ├── index.js // 入口文件
│ └── router.js // 項目的路由文件
├── .eslintrc // bower安裝目錄的配置
├── .editorconfig // 保證代碼在不一樣編輯器可視化的工具
├── .gitignore // git上傳時忽略的文件
├── .roadhogrc.js // 項目的配置文件,配置接口轉發,css_module等都在這邊。
├── .roadhogrc.mock.js // 項目的配置文件
└── package.json // 當前整一個項目的依賴
複製代碼
route/IndexPage.js
import React from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';
class IndexPage extends React.Component {
render() {
const { dispatch } = this.props;
return (
<div className={styles.normal}>
<div className={styles.record}>Highest Record: 1</div>
<div className={styles.current}>2</div>
<div className={styles.button}>
<button onClick={() => {}}>+</button>
</div>
</div>
);
}
}
export default connect()(IndexPage);
複製代碼
routes/IndexPage.css
.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;
}
複製代碼
model
裏面去處理 state
,在頁面輸出 model
中的 state
index.js
中將models/example.js
,即將model下一行的的註釋打開。import './index.css';
import dva from 'dva';
import model from './models/example'
import router from './router'
// 1. Initialize 建立dva實列
const app = dva();
// 2. Plugins 裝載插件(可選)
// app.use({});
// 3. Model 註冊modal
app.model(model);
// 4. Router 配置路由
app.router(router);
// 5. Start 啓動應用
app.start('#root');
複製代碼
models/example.js
,將namespace
名字改成 count
,state
對象加上 record
與 current
屬性export default {
namespace: 'count',
state: {
record: 0,
current: 0,
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-lines
},
},
effects: {
*fetch({ payload }, { call, put }) { // eslint-disable-line
yield put({ type: 'save' });
},
},
reducers: {
save(state, action) {
return { ...state, ...action.payload };
},
},
};
複製代碼
routes/indexpage.js
頁面,經過的 mapStateToProps
引入相關的 state
。import React from "react";
import { connect } from "dva";
import styles from "./IndexPage.css";
class IndexPage extends React.Component {
render() {
const { dispatch, count } = this.props;
return (
<div className={styles.normal}>
<div className={styles.record}>Highest Record: {count.record}</div>
{/* // 將count的record輸出 */}
<div className={styles.current}>{count.current}</div>
<div className={styles.button}>
<button
onClick={() => {
}}
>
+
</button>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return { count: state.count };
} // 獲取state
export default connect(mapStateToProps)(IndexPage);
複製代碼
mapStateToProps
/mapDispatchToProps
)將須要的
state
的節點注入到與此視圖數據相關的組件上node
function mapStateToProps(state, ownProps) {
return {
loading:state.getIn(['projectPre', 'projectMgr', 'loading']),
data:state.getIn(['APP', 'data']),
...
}
// loading、data都是來自對應的reduce
}
複製代碼
將須要綁定的響應事件注入到組件上react
function mapDispatchToProps(dispatch){
return {
...bindActionCreators(action, dispatch)
}
}
// mapDispatchToProps()函數的bindActionCreators、action須要引入
// import * as action from '../action';
// import { bindActionCreators } from 'redux';
------------------------------------
------------------------------------
// 多個action 引入
import * as action from '../action';
import * as action2 from '../../../inde/action';
function mapDispatchToProps(dispatch){
return {
...bindActionCreators(action, dispatch)
...bindActionCreators(action2, dispatch)
}
}
------------------------------------
------------------------------------
// 引入一個action裏面的多個方法
import { activeOrAbandonedApproval, showSeller, getAddOrderDiscounts } from '../orderInfo/action'
function mapDispatchToProps(dispatch) {
return {
...bindActionCreators({ activeOrAbandonedApproval, showSeller, getAddOrderDiscounts }, dispatch)
}
}
複製代碼
+
發送 action
,經過 reducer
改變相應的 state
export default {
...
reducers: {
add1(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 };
},
},
};
複製代碼
models/example.js
,寫相應的 reducer
。export default {
namespace: "count",
state: {
record: 0,
current: 0
},
subscriptions: {
setup({ dispatch, history }) {
// eslint-disable-lines
}
},
effects: {
*fetch({ payload }, { call, put }) {
// eslint-disable-line
yield put({ type: "save" });
}
},
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 };
}
}
};
複製代碼
routes/IndexPage.js
中 +
號點擊的時候,dispatch
一個 action
import React from "react";
import { connect } from "dva";
import styles from "./IndexPage.css";
class IndexPage extends React.Component {
componentDidMount() {
console.log(this.props);
}
render() {
const { dispatch, count } = this.props;
return (
<div className={styles.normal}>
<div className={styles.record}>Highest Record: {count.record}</div>
{/* // 將count的record輸出 */}
<div className={styles.current}>{count.current}</div>
<div className={styles.button}>
<button
onClick={() => {
dispatch({ type: "count/add" });
}}
>
+
</button>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return { count: state.count };
} // 獲取state
export default connect(mapStateToProps)(IndexPage);
複製代碼
effect
模擬一個數據接口請求,返回以後,經過 yield put()
改變相應的state
models/example.js
的 effect
effects: {
*add(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'minus' });
},
},
複製代碼
delay
,寫的一個延時的函數,咱們在 utils
裏面編寫一個 utils.js
,通常請求接口的函數都會寫在 servers
文件夾中。export function delay(timeout) {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}
複製代碼
models/example.js
導入這個 utils.js
import { delay } from '../utils/utils';
複製代碼
subscriptions
,當用戶按住command+up
時候觸發添加數字的 action
npm install keymaster --save
複製代碼
models/example.js
中做以下修改,這裏windows
中的up
就是鍵盤的↑
.import key from 'keymaster';
...
app.model({
namespace: 'count',
+ subscriptions: {
+ keyboardWatcher({ dispatch }) {
+ key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
+ },
+ },
});
複製代碼
+
按鈕以後,咱們會看到current
會不斷加一,可是1s事後,他會自動減到零。那麼咱們如何修改呢?effect
中發送一個關於添加的action
,可是咱們在effect
中不能直接這麼寫effects: {
*add(action, { call, put }) {
yield put({ type: 'add' });
yield call(delay, 1000);
yield put({ type: 'minus' });
},
},
複製代碼
effect
與reducers
中的add
方法重合了,這裏會陷入一個死循環,由於當組件發送一個dispatch
的時候,model
會首先去找effect
裏面的方法,當又找到add
的時候,就又會去請求effect
裏面的方法。reducers
裏面的方法,使它不與effect
的方法同樣,將reducers
中的add
改成add1
.reducers: {
add1(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};
},
},
effects: {
*add(action, { call, put }) {
yield put({ type: 'add1' });
yield call(delay, 1000);
yield put({ type: 'minus' });
},
},
複製代碼
關於dva
框架的model
總結git
state
這裏的
state
跟以前用state
的概念是同樣的,只不過她的優先級比初始化的低,但在項目中的state
基本都是在這裏定義的。github
namespace
model
的命名空間,同時也是他在全局state
上的屬性,只能用字符串,咱們發送在發送action
到相應的reducer
時,就會須要用到namespace
。web
Reducer
以
key/value
格式定義reducer
,用於處理同步操做,惟一能夠修改state
的地方。由action
觸發。其實一個純函數。npm
Effect
用於處理異步操做和業務邏輯,不直接修改
state
,簡單的來講,就是獲取從服務端獲取數據,而且發起一個action
交給reducer
的地方。json
其中它用到了redux-saga
,裏面有幾個經常使用的函數。redux
*add(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'minus' });
},
複製代碼
Effects
(1) put
,用於觸發actionwindows
yield put({ type: '',payload:'' });
複製代碼
(2)call
用於異步邏輯處理,支持Promise
const result= yield call(fetch,'');
複製代碼
(3)select
用於state獲取數據
const todo = yield select(state=>state.todo);
複製代碼
Subscription
subscription
是訂閱,用於訂閱一個數據源,而後根據須要dispatch
相應的actio
n。在app.start()
時被執行,數據源能夠是當前的時間、當前頁面的url
、服務器的websocket
鏈接、history
路由變化等等。