dva使用及项目搭建
2020-01-14

一、简介

  本文将简单分析dva脚手架的使用及项目搭建过程。

  首先,dva是一个基于redux和redux-saga的数据流方案,然后为了简化开发体验,dva还额外内置了react-router和fetch,所以也可以理解为一个轻量级的应用框架。

二、特性

  易用易学、elm概念、插件机制、支持HMR。

三、环境搭建

1、首先安装dva-cli

npm install dva-cli -g

2、初始化项目

dva new dva-quickstartcd dva-quickstartnpm 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. Initializeconst app = dva();// 2. Plugins// app.use({});// 3. Modelapp.model(require("./models/example").default);app.model(require("./models/todos").default);// 4. Routerapp.router(require("./router").default);// 5. Startapp.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. Initializeconst app = dva();// 2. Plugins// app.use({});// 3. Modelapp.model(require("./models/example").default);app.model(require("./models/todos").default);// 4. Routerapp.router(require("./router").default);// 5. Startapp.start("#root");