一、简介

  本文将简单分析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、目录结构

dva使用及项目搭建-LMLPHP

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');

dva使用及项目搭建-LMLPHP

11-18 20:51