關於dva框架的簡單操作以及demo

sunshinezhong發表於2018-05-14

關於dva的心得

由於之前都用react+redux。這次接觸到dva框架簡單學習下。 本篇文章主要講解了dva框架中開發常用API和一些使用技巧,如果想檢視更多更全面的API,請參照dva官方文件: 官方demo

簡單的安裝步驟

1.首先全域性安裝dva-cli

$ npm install -g dva-cli
複製程式碼

2.接著使用dva-cli建立我們的專案資料夾,這裡mydva是專案名字,自己隨意。

$ dva new mydva
複製程式碼

3.進入mydva目錄,安裝依賴,執行如下操作。

$ cd myapp
$ npm start
複製程式碼

4.啟動成功之後介面如下

關於dva框架的簡單操作以及demo

5.檔案目錄以及分析

關於dva框架的簡單操作以及demo

├── 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 // 當前整一個專案的依賴
複製程式碼

簡單的demo,參考 官方的計算的demo

1.首先來修改 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);
複製程式碼

2.其次來修改樣式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;
}
複製程式碼

3.介面顯示如下

關於dva框架的簡單操作以及demo

4.在model 裡面去處理 state ,在頁面輸出 model 中的 state

(1)首先我們在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');
複製程式碼
(2)接下來我們進入 models/example.js,將namespace 名字改為 countstate 物件加上 recordcurrent 屬性
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 };
    },
  },

};

複製程式碼
(3)接著我們來到 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);
複製程式碼
(4)這裡需要說明下關於 React依賴注入說明(mapStateToProps/mapDispatchToProps

將需要的state的節點注入到與此檢視資料相關的元件上

function mapStateToProps(state, ownProps) {
    return {
            loading:state.getIn(['projectPre', 'projectMgr', 'loading']),
            data:state.getIn(['APP', 'data']),
      ...
    }
    // loading、data都是來自對應的reduce
}
複製程式碼

將需要繫結的響應事件注入到元件上

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)
  }
}
複製程式碼
(5)通過+傳送 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 };
    },
  },
};
複製程式碼
(6)首先我們在 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 };
    }
  }
};
複製程式碼
(7)在頁面的模板 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);
複製程式碼
效果如下:

關於dva框架的簡單操作以及demo

5.接下來我們來使用 effect 模擬一個資料介面請求,返回之後,通過 yield put() 改變相應的state

(1)首先我們替換相應的 models/example.jseffect
effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
},
複製程式碼
(2)這裡的 delay,寫的一個延時的函式,我們在 utils 裡面編寫一個 utils.js ,一般請求介面的函式都會寫在 servers資料夾中。
export function delay(timeout) {
  return new Promise((resolve) => {
    setTimeout(resolve, timeout);
  });
}
複製程式碼
(3)接著在 models/example.js 匯入這個 utils.js
import { delay } from '../utils/utils';
複製程式碼

6.訂閱訂閱鍵盤事件,使用 subscriptions,當使用者按住command+up 時候觸發新增數字的 action

(1)你需要安裝 keymaster 這個依賴
npm install keymaster --save
複製程式碼
(2)在models/example.js中作如下修改,這裡windows中的up就是鍵盤的.
import key from 'keymaster';
...
app.model({
  namespace: 'count',
+  subscriptions: {
+    keyboardWatcher({ dispatch }) {
+      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
+    },
+  },
});
複製程式碼

7.例子中我們看到當我們不斷點選+按鈕之後,我們會看到current會不斷加一,但是1s過後,他會自動減到零。那麼我們如何修改呢?

(1)我們應該在effect中傳送一個關於新增的action,但是我們在effect中不能直接這麼寫
effects: {
    *add(action, { call, put }) {
      yield put({ type: 'add' });
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
複製程式碼
因為如果這樣的話,effectreducers中的add方法重合了,這裡會陷入一個死迴圈,因為當元件傳送一個dispatch的時候,model會首先去找effect裡面的方法,當又找到add的時候,就又會去請求effect裡面的方法。
(2)應該更改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框架的簡單操作以及demo

結語

關於dva框架的model總結

1.state

這裡的state 跟之前用 state 的概念是一樣的,只不過她的優先順序比初始化的低,但在專案中的 state 基本都是在這裡定義的。

2.namespace

model 的名稱空間,同時也是他在全域性 state 上的屬性,只能用字串,我們傳送在傳送 action 到相應的 reducer 時,就會需要用到namespace

3.Reducer

key/value 格式定義 reducer,用於處理同步操作,唯一可以修改 state 的地方。由 action 觸發。其實一個純函式。

4.Effect

用於處理非同步操作和業務邏輯,不直接修改 state,簡單的來說,就是獲取從服務端獲取資料,並且發起一個 action 交給 reducer的地方。

其中它用到了redux-saga,裡面有幾個常用的函式。

*add(action, { call, put }) {
    yield call(delay, 1000);
    yield put({ type: 'minus' });
},
複製程式碼
5.Effects

(1) put,用於觸發action

yield put({ type: '',payload:'' });
複製程式碼

(2)call 用於非同步邏輯處理,支援Promise

const result= yield call(fetch,'');
複製程式碼

(3)select 用於state獲取資料

const todo = yield select(state=>state.todo);
複製程式碼
6.Subscription

subscription 是訂閱,用於訂閱一個資料來源,然後根據需要 dispatch 相應的 action。在 app.start() 時被執行,資料來源可以是當前的時間、當前頁面的url、伺服器的 websocket 連線、history路由變化等等。

相關文章