1. 程式人生 > >dva使用及專案搭建

dva使用及專案搭建

一、簡介

  本文將簡單分析dva腳手架的使用及專案搭建過程。

  首先,dva是一個基於redux和redux-saga的資料流方案,然後為了簡化開發體驗,dva還額外內建了react-router和fetch,所以也可以理解為一個輕量級的應用框架。

二、特性

  易用易學、elm概念、外掛機制、支援HMR。

三、環境搭建

1、首先安裝dva-cli

npm install dva-cli -g

2、初始化專案

dva new dva-quickstart
cd dva-quickstart
npm start

3、引入antd

  通過 npm 安裝 antd 和 babel-plugin-import

 。babel-plugin-import 是用來按需載入 antd 的指令碼和樣式的.

npm install antd babel-plugin-import --save

4、按需載入,找到根目錄下面的.webpackrc檔案,並在檔案中新增外掛配置。

"extraBabelPlugins": [
    ["import", { "libraryName": "antd", "style": "css" }]
]

5、試引入ant 元件button

import React from 'react';
import { connect } from 'dva';
import styles from 
'./IndexPage.css'; import { Button } from 'antd' function IndexPage() { return ( <div className={styles.normal}> <h1 className={styles.title}>Yay! Welcome to dva!</h1> <Button type="primary">primary</Button> <div className={styles.welcome} /> <ul className={styles.list}> <li>Getting Started</li> </ul> </div> ); } IndexPage.propTypes
= { }; export default connect()(IndexPage);

四、專案目錄結構介紹

1、目錄結構

assets目錄:一般作為靜態檔案儲存目錄,比如圖片或者css;

components:元件目錄;

models:應用邏輯層,可存放公共的資料以及邏輯,類似於vuex;

pages(routes):頁面路由存放資料夾;

services:頁面API請求資料;

utils:公共方法的封裝;

index.js:入口檔案;

router.js:路由檔案

2、具體檔案介紹

2.1、index.js  入口檔案

import dva from 'dva';
import './index.css';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model
app.model(require('./models/example').default);
app.model(require('./models/todos').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');

2.2 router.js路由檔案

import React from 'react';
import { Route, Switch } from 'dva/router';

import dynamic from 'dva/dynamic' // 路由按需載入
import { ConnectedRouter } from 'react-router-redux';
import App from './pages/App'

function RouterConfig({ history,app }) {
    const IndexPage = dynamic({
        app,
        component:(()=> import('./pages/IndexPage/IndexPage'))
    })
    const Users = dynamic({
        app,
        component:(()=> import('./pages/UserPage/UserPage'))
    })
    const List = dynamic({
        app,
        component:(()=> import('./pages/ListPage/ListPage'))
    })
    return (
        <ConnectedRouter history={history}>
            <App>
                <Switch>
                    <Route path="/" exact component={IndexPage}/>
                    <Route path="/users" exact component={Users}></Route>
                    <Route path="/list" exact component={List}></Route>
                </Switch>
            </App>
        </ConnectedRouter>
    );
}

2.3 頁面元件IndexPage.js

import React from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';

import { Button } from 'antd'

function IndexPage() {
  return (
    <div className={styles.normal}>
        <h1 className={styles.title}>Yay! Welcome to dva!</h1>
        <Button type="primary">primary</Button>
        <div className={styles.welcome} />
        <ul className={styles.list}>
            <li>Getting Started</li>
        </ul>
    </div>
  );
}

IndexPage.propTypes = {
};

export default connect()(IndexPage);

五、connect()方法介紹

  connect 是一個函式,繫結 State 到 View。

import { connect } from 'dva';

function mapStateToProps(state) {
  return { todos: state.todos };
}
connect(mapStateToProps)(App);

  connect 方法返回的也是一個 React 元件,通常稱為容器元件。因為它是原始 UI 元件的容器,即在外面包了一層 State。

  connect 方法傳入的第一個引數是 mapStateToProps 函式,mapStateToProps 函式會返回一個物件,用於建立 State 到 Props 的對映關係。

六、dispatch方法

  dispatch 是一個函式方法,用來將 Action 傳送給 State。

dispatch({
  type: 'click-submit-button',
  payload: this.form.data
})

  type:方法名;

  payload:引數

  dispatch 方法從哪裡來?被 connect 的 Component 會自動在 props 中擁有 dispatch 方法。

 七、model層介紹

  比較常用的model成如下

{
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1 },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
}

1.namespace:名稱空間;當前 Model 的名稱。整個應用的 State,由多個小的 Model 的 State 以 namespace 為 key 合成

2.state:該 Model 當前的狀態。資料儲存在這裡,直接決定了檢視層的輸出

3.reducers: Action 處理器,處理同步動作,用來算出最新的 State;

4.effects:Action 處理器,處理非同步動作

 注:函式名前邊帶一個*號,是一個生成器(Generator )函式,內部使用 yield 關鍵字,標識每一步的操作(不管是非同步或同步)。

dva 提供多個 effect 函式內部的處理函式,比較常用的是 call 和 put

  call:執行非同步函式

  put:發出一個 Action,類似於 dispatch

八、demo  TODOLIst  實現

1.首先在components下新建一個TodoList.js檔案

import React from 'react';
class TodoList extends React.Component{
    constructor(props) {
        super(props);
        this.state={
            value:''
        }
    }
    addTodo(e){
        if (e.keyCode===13) {
            const todo = e.target.value;

            this.props.dispatch({
                type: 'todos/addTodo',
                payload: todo
            })
            this.setState({
                value: ''
            })
        }
    }
    deleteTodo(index){
        this.props.dispatch({
            type: 'todos/deleteTodo',
            payload: index
        })
    }
    render() {
        const todoList = this.props.todoList.map((val, index) => {
    
          return <div key={index}>
            <span>{val.value}</span>
            <button onClick={() => this.deleteTodo(index)}>X</button>
          </div>
        });
    
        let count = 0;
    
        this.props.todoList.map(item => count = !item.finished ? count + 1 : count);
    
        return (
          <div>
            <h3>待辦事項有:{count}</h3>
            <input placeholder="please input"
                   value={this.state.value}
                   onChange={(e) => this.setState({value: e.target.value})}
                   onKeyDown={(e) => this.addTodo(e)}/>
            <div>
              {todoList}
            </div>
          </div>
        )
    }
}
    
export default TodoList;

程式碼中:通過dispatch 派送一個action,type為action名稱,payload為傳遞引數

 this.props.dispatch({
                type: 'todos/addTodo',
                payload: todo
            })

2.新建路由頁面LIstPage.js

import {connect} from 'dva';
import TodoList from '../../components/TodoList';

const mapStateToProps = (state) => {

    return {
      todoList: state.todos.todoList
    }
  };
  
  export default connect(mapStateToProps)(TodoList);

通過mapStateToProps 方法將model裡的todoList放回到頁面元件的props.todoList;

3.新建一個model todos.js

import queryString from 'query-string';
import * as todoService from '../services/todo'

export default {
    namespace: 'todos',
    state: {todoList: []},
    reducers: {
        save(state, {payload: {todoList}}) {
            return {...state, todoList}
        }
    },
    effects: {
        * addTodo({payload: value}, {call, put, select}) {
            // 模擬網路請求
            const data = yield call(todoService.query, value);
            let tempList = yield select(state => state.todos.todoList);
            let todoList = [];
            todoList = todoList.concat(tempList);
            const tempObj = {};
            tempObj.value = value;
            tempObj.id = todoList.length;
            todoList.push(tempObj);
            yield put({type: 'save', payload: {todoList}})
        },
        * deleteTodo({payload: index}, {call, put, select}) {
            const data = yield call(todoService.query, index);
            let tempList = yield select(state => state.todos.todoList);
            let todoList = [];
            todoList = todoList.concat(tempList);
            todoList.splice(index, 1);
            yield put({type: 'save', payload: {todoList}})
        },
    },
    subscriptions: {
        setup({dispatch, history}) {
            // 監聽路由的變化,請求頁面資料
            return history.listen(({pathname, search}) => {
                const query = queryString.parse(search);
                let todoList = [];
        
                if (pathname === 'todos') {
                    dispatch({type: 'save', payload: {todoList}})
                }
            })
        }
    }
}

一般來說,effects做主要的邏輯計算,reducers做資料儲存,通過複雜的邏輯計算後,把處理好的資料呼叫reducers的方法進行資料儲存。

4.在index.js進行model以及路由注入

import dva from 'dva';
import './index.css';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model
app.model(require('./models/example').default);
app.model(require('./models/todos').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');