dva的入门和使用

作者: 有田春雪 | 来源:发表于2018-07-20 09:30 被阅读1526次

    dva的入门和使用

    创建项目

    1. 安装cli npm i dva-cli -g
    2. 创建新项目 dva new [project name]
    3. 启动项目 npm run start

    目录介绍

        |- mock                     // 存放用于 mock 数据的文件
        |- node_modules
        |- package.json
        |- public                   // 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist)
        |- src                      // 文件夹用于存放项目源代码
            |- asserts                  // 用于存放静态资源,打包时会经过 webpack 处理
            |- components               // 用于存放 React 组件,一般是该项目公用的无状态组件
            |- models                   // 用于存放模型文件
            |- routes                   // 用于存放需要 connect model 的路由组件
            |- services                 // 用于存放服务文件,一般是网络请求等
            |- utils                    // 工具类库
            |- router.js                // 路由文件
            |- index.js                 // 项目的入口文件
            |- index.css                // 一般是共用的样式
        |- .editorconfig            // 编辑器配置文件
        |- .eslintrc                // ESLint配置文件
        |- .gitignore               // Git忽略文件
        |- .roadhogrc.mock.js       // Mock配置文件
        |- .webpackrc               // 自定义的webpack配置文件,JSON格式,如果需要 JS 格式,可修改为 webpackrc.js
    

    使用antd

    1. 安装 npm i antd babel-plugin-import -S
    2. 配置 .roadhogrc 文件 / 或者 .webpackrc 文件
          "extraBabelPlugins": [
                "transform-runtime",
                ["import", {"libraryName":"antd", "style":"css"}]
            ]
      

    配置代理

        // 在 .roadhogrc 文件, 加上 “proxy” 配置 / 或者 `.webpackrc` 文件
        "proxy": {
            "/api": {
                "target": "http://localhost:9093/",
                "changeOrigin": true,
                "pathRewrite": {"^/api": ""}
            }
        }
    

    HMR热更新替换

        // 在 .webpackrc 中添加如下配置
    
        "env": {
            "development": {
                "extraBabelPlugins": [
                    "dva-hmr"
                ]
            }
        }
        // 如果无效,请尝试更新一下 babel-plugin-dva-hmr
    

    开发简单的项目示例

    1. 创建项目, 完成上述配置

    2. 创建后台接口, 也可mock数据

      • 我自己是用nodejs创建的接口
          // 创建与src同级的文件夹 server
          // 在server下创建server.js  model.js   api.js
          
          // server.js ===============================================================
      
          const express = require('express')
          const bodyParser = require('body-parser')   // 解析传输的body
          const apiRoute = require('./api')    // 引入路由
      
          // 创建app
          const app = express()
      
          // 使用中间件
          app.use(bodyParser.json())
          app.use(bodyParser.urlencoded({ extended: false }))
      
          // 使用路由
          app.use('/api', apiRoute)
      
          // 开启服务
          app.listen(9093, () => {
              console.log('Node app start at port 9093')
          })
      
      
          // model.js ===============================================================
      
           // 引入mongoose库, 该库是nodejs来操作mongodb的
           const mongoose = require('mongoose')
      
           // 连接mongo, 并且使用dva-todo这个集合
           const DB_URL = 'mongodb://localhost:27017/dva-todo'
           mongoose.connect(DB_URL)
      
          // 定义表结构
          const models = {
              todo: {
                  'text': {type: String, require: true},
                  'checked': {type: Boolean, require: true},
              }
          }
      
          // 快速生成对应结构的表
          for (let m in models){
              mongoose.model(m, new mongoose.Schema(models[m]))
          }
      
          // 输出获取数据库的方法
          module.exports = {
              getModel(name) {
                  return mongoose.model(name)
              }
          }
      
      
          // api.js ================================================================
          
          const express = require('express')  // 加载express框架
          const Router = express.Router()     // 使用express的路由
          const apiModel = require('./model')    // 引入数据库操作
          const todo = apiModel.getModel('todo') // 使用数据库的todo表
          const multipart = require('connect-multiparty') // 使得post请求能支持formdata
          const multipartMiddleware = multipart()
      
          // 查询数据
          Router.get('/get', (req, res) => {
              todo.find({}, (err, doc) => {
                  if(err) return res.json({code: 1, msg: '获取数据失败!'})
                  return res.json({code: 0, data: doc})
              })
          })
      
          // 添加数据
          Router.post('/add', multipartMiddleware, (req, res) => {
              const {text} = req.body
              const newTodo = new todo({text: text, checked: false})
              newTodo.save((err, doc) => {
                  if(err) return res.json({code: 1, msg: '添加数据失败!'})
                  return res.json({code: 0, data: doc})
              })
          })
      
          // 选中
          Router.post('/check', (req, res) => {
              const {id, checked} = req.body
              todo.findByIdAndUpdate(id, {checked: checked}, (err, doc) => {
                  if(err) return res.json({code: 1, msg: '删除数据失败!'})
                  return res.json({code: 0, data: doc})
              })
          })
      
          // 全选或反选
          Router.post('/checkAll', (req, res) => {
              const {checked} = req.body
              todo.update({}, {'$set': {checked: checked}}, { multi: true }, (err, doc) => {
                  if(err) return res.json({code: 1, msg: '删除数据失败!'})
                  return res.json({code: 0})
              })
          })
      
      
          // 删除
          Router.post('/delete', (req, res) => {
              const {id} = req.body
              todo.remove({_id:id}, (err, doc) => {
                  if(err) return res.json({code: 1, msg: '删除数据失败!'})
                  return res.json({code: 0})
              })
          })
      
          // 清空
          Router.get('/clear', (req, res) => {
              todo.remove({}, (err, doc) => {
                  if(err) return res.json({code: 1, msg: '清空数据失败!'})
                  return res.json({code: 0})
              })
          })
      
          // 输出路由
          module.exports = Router
      
    3. src/index.js => dva的入口

    import dva from 'dva'
    import './index.css'
    import createLoading from 'dva-loading'  // 需要npm安装
    import createHistory from 'history/createBrowserHistory'
    
    // 1. Initialize
    const app = dva({
        history: createHistory()
    })
    
    // 2. Plugins
    // app.use({});
    app.use(createLoading())
    
    // 3. Model
    app.model(require('./models/main').default)
    
    // 4. Router
    app.router(require('./router').default)
    
    // 5. Start
    app.start('#root')
    
    1. src/index.css => 公共的css
    html, body, :global(#root) {
      height: 100%;
    }
    
    1. src/router.js => 路由配置
    import React from 'react'
    import { Router, Route, Switch } from 'dva/router'
    import All from './routes/all/all'
    import Active from './routes/active/active'
    import Completed from './routes/completed/completed'
    
    function RouterConfig ({ history }) {
      return (
        <Router history={history}>
          <Switch>
            <Route path="/all" exact component={All} />
            <Route path="/active" exact component={Active} />
            <Route path="/completed" exact component={Completed} />
            <Route component={All} />
          </Switch>
        </Router>
      )
    }
    
    export default RouterConfig
    
    1. src/utils/request.js => fetch请求数据的配置
    import fetch from 'dva/fetch'
    
    function parseJSON(response) {
      return response.json()
    }
    
    function checkStatus(response) {
      if (response.status >= 200 && response.status < 300) {
        return response
      }
    
      const error = new Error(response.statusText)
      error.response = response
      throw error
    }
    
    /**
     * Requests a URL, returning a promise.
     *
     * @param  {string} url       The URL we want to request
     * @param  {object} [options] The options we want to pass to "fetch"
     * @return {object}           An object containing either "data" or "err"
     */
    export default function request(url, options) {
      const defaultOptions = {
        credentials: 'include',
      }
      const newOptions = { ...defaultOptions, ...options }
    
      if (newOptions.method === 'POST' || newOptions.method === 'PUT' || newOptions.method === 'DELETE') {
        if (!(newOptions.body instanceof FormData)) {
          newOptions.headers = {
            Accept: 'application/json',
            'Content-Type': 'application/json; charset=utf-8',
            ...newOptions.headers,
          }
          newOptions.body = JSON.stringify(newOptions.body)
        } else {
          // newOptions.body is FormData
          newOptions.headers = {
            Accept: 'application/json',
            ...newOptions.headers,
          }
        }
      }
    
    
      return fetch(url, newOptions)
        .then(checkStatus)
        .then(parseJSON)
        .then(data => ({ data }))
        .catch(err => ({ err }))
    }
    
    1. src/services/main.js => 配置各种接口
    import request from '../utils/request'
    import {stringify} from 'qs'
    
    export function get() {
      return request('/api/get')
    }
    
    export function add(obj) {
      return request('/api/add', {method: 'POST', body: obj})
    }
    
    export function remove(obj) {
      return request('/api/delete', {method: 'POST', body: obj})
    }
    
    export function check(obj) {
      return request('/api/check', {method: 'POST', body: obj})
    }
    
    export function clear() {
      return request('/api/clear')
    }
    
    export function checkAll(obj) {
      return request('/api/checkAll', {method: 'POST', body: obj})
    }
    
    1. src/routes => 路由页面
    // src/routes/all/all.js ====================================================
    import React from 'react'
    import { connect } from 'dva'
    import { Row, Col } from 'antd'
    import Header from '../../components/header'
    import Footer from '../../components/footer'
    import MainList from '../../components/main'
    
    class All extends React.Component{
        render (){
          return (
            <div style={{height: '100%'}}>
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#001529'}}>
                <Col>
                  <Header></Header>
                </Col>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '80%', background: '#f0f2f5'}}>
                <MainList dataType='all'/>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#f0f2f5'}}>
                <Col>
                  <Footer></Footer>
                </Col>
              </Row>
            </div>
          )
        }
    }
    
    All.propTypes = {}
    
    export default connect()(All)
    
    
    
    // src/routes/active/active.js========================================================================
    import React from 'react'
    import { connect } from 'dva'
    import { Row, Col } from 'antd'
    import Header from '../../components/header'
    import Footer from '../../components/footer'
    import MainList from '../../components/main'
    
    class Active extends React.Component{
        render (){
          return (
            <div style={{height: '100%'}}>
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#001529'}}>
                <Col>
                  <Header></Header>
                </Col>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '80%', background: '#f0f2f5'}}>
                <MainList dataType='active'/>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#f0f2f5'}}>
                <Col>
                  <Footer></Footer>
                </Col>
              </Row>
            </div>
          )
        }
    }
    
    Active.propTypes = {}
    
    export default connect()(Active)
    
    
    
    // src/routes/completed/completed.js=====================================================================
    import React from 'react'
    import { connect } from 'dva'
    import { Row, Col } from 'antd'
    import Header from '../../components/header'
    import Footer from '../../components/footer'
    import MainList from '../../components/main'
    
    class Completed extends React.Component{
        render (){
          return (
            <div style={{height: '100%'}}>
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#001529'}}>
                <Col>
                  <Header></Header>
                </Col>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '80%', background: '#f0f2f5'}}>
                <MainList dataType='completed'/>
              </Row>
    
              <Row type="flex" justify="center" style={{height: '10%', alignItems: 'center', background: '#f0f2f5'}}>
                <Col>
                  <Footer></Footer>
                </Col>
              </Row>
            </div>
          )
        }
    }
    
    Completed.propTypes = {}
    
    export default connect()(Completed)
    
    1. src/models/main.js => dva最重要的部分, 将redux全都集中在一个文件进行操作
    import {add, get, remove, check, clear, checkAll} from '../services/main'
    
    export default {
    
      // 命名空间, 以命名空间来区分不同的model
      namespace: 'todo',   
    
      // 初始state
      state: [],    
    
      // 监听任务 => 一般用来监听路由变化
      subscriptions: {    
        setup({ dispatch, history }) {  // eslint-disable-line
          history.listen(location => {
            // console.log(1, location)
          })
        },
      },
    
      // 进行各种异步操作, 组件内可以dispatch来触发, 但是此处不能修改state, 只有reducers可以
      effects: {
        *get({ payload }, { call, put }) {  // eslint-disable-line
          const response = yield call(get, payload)
          const data = response.data.code === 0 ? response.data.data : []
          yield put({ type: 'getData' , payload: data})
        },
        *clear({ payload }, { call, put }) {
          const response = yield call(clear, payload)
          if(response.data.code === 0){
            yield put({ type: 'clearData'})
          }else{
            console.error(response.data.msg)
          }
        },
        *add({ payload }, { call, put }) {
          const response = yield call(add, payload)
          if(response.data.code === 0){
            yield put({ type: 'addData', payload: response.data.data})
          }else{
            console.error(response.data.msg)
          }
        },
        *remove({ payload }, { call, put }) {
          const response = yield call(remove, payload)
          if(response.data.code === 0){
            yield put({ type: 'removeData', payload: payload})
          }else{
            console.error(response.data.msg)
          }
        },
        *check({ payload }, { call, put }) {
          const response = yield call(check, payload)
          if(response.data.code === 0){
            yield put({ type: 'checkData', payload: response.data.data})
          }else{
            console.error(response.data.msg)
          }
        },
        *checkAll({ payload }, { call, put }) {
          const response = yield call(checkAll, payload)
          if(response.data.code === 0){
            yield put({ type: 'checkAllData', payload: payload})
          }else{
            console.error(response.data.msg)
          }
        }
      },
    
      // 修改state的各种操作, 可以通过effects进行调用, 也可以通过组件dispatch进行调用
      reducers: {
        getData(state, action) {
          return [ ...state, ...action.payload ]
        },
        clearData(state) {
          state = []
          return state
        },
        addData(state, action) {
          return [...state, {_id: action.payload._id, text: action.payload.text, checked: false}]
        },
        removeData(state, action) {
          const id = action.payload.id
          return state.filter(v => v._id !== id)
        },
        checkData(state, action) {
          const {_id, checked} = action.payload
          state.forEach(v => {
            if(v._id === _id){
              v.checked = !checked
            }
          })
          return state
        },
        checkAllData(state, action) {
          state.forEach(v => {
            v.checked = action.payload.checked
          })
          return state
        }
      },
    }
    
    1. src/components => 各个组件
     // src/components/header.js=============================================================
    import React from 'react'
    
    const Header = () => {
      return (
        <div>
          <h1 style={{color: '#fff'}}>todos</h1>
        </div>
      )
    }
    
    Header.propTypes = {
    }
    
    export default Header
    
    
    // src/components/footer.js=============================================================
    import React from 'react'
    
    const Footer = () => {
      return (
        <div>
          <a href='https://github.com/OlivierHu/dva-redux/tree/master/experience_dva' style={{color: '#999'}}>版权所有 @OlivierHu</a>
        </div>
      )
    }
    
    Footer.propTypes = {}
    
    export default Footer
    
    
    // src/components/main.js================================================================
    import React from 'react'
    import { connect } from 'dva'
    import { Link } from 'dva/router'
    import PropTypes from 'prop-types'
    import { Row, Col, Form, Input, Checkbox, Button, List, Icon } from 'antd'
    
    // 此处的connect是装饰器的写法, 需要配置装饰器
    @connect(state => ({
      data: state
    }))
    
    export default class MainList extends React.Component{
      static propTypes = {
        dataType: PropTypes.string.isRequired
      }
    
      constructor(props){
        super(props)
        this.state = {
          showClose: true
        }
        this.handleAdd = this.handleAdd.bind(this)
        this.deleteItem = this.deleteItem.bind(this)
        this.clearData = this.clearData.bind(this)
        this.checkAll = this.checkAll.bind(this)
      }
    
      componentDidMount(){
        const { dispatch } = this.props
        this.props.data.todo = []
        dispatch({
          type: 'todo/get',
          payload: {}
        })
      }
    
      clearData(){
        this.props.dispatch({type: 'todo/clear'})
        this.props.dispatch({type: 'todo/get',payload: {}})
      }
    
      handleAdd(e){
        if(e.keyCode !== 13) return
        const text = e.target.value
        e.target.value = ''
        const { dispatch } = this.props
        dispatch({
          type: 'todo/add',
          payload: {text}
        })
      }
    
      deleteItem(id){
        this.props.dispatch({
          type: 'todo/remove',
          payload: {id}
        })
      }
    
      handleCheck(id, checked) {
        this.props.dispatch({
          type: 'todo/check',
          payload: {id, checked: !checked}
        })
      }
    
      checkAll() {
        const tempData = Object.values(this.props.data.todo)
        const flag = tempData.every(v => v.checked === true)
        this.props.dispatch({
          type: 'todo/checkAll',
          payload: {checked: !flag}
        })
      }
    
      render() {
        const FormItem = Form.Item
        const ListItem = List.Item
        let allData = []
        allData = Object.values(this.props.data.todo)
        
        const leftNum = allData.filter(v => v.checked === false).length
    
        if(this.props.dataType === 'active'){
          allData = allData.filter(v => v.checked === false)
        }else if(this.props.dataType === 'completed'){
          allData = allData.filter(v => v.checked === true)
        }
    
        return (
          <div style={{width: '100%'}}>
            <Row style={{minHeight: 50}}></Row>
            <Row>
              <Col span={12} offset={6}>
                <Form>
                  <FormItem>
                    <Button icon="down" onClick={this.checkAll}></Button>
                    <Input style={{width: 'calc(100% - 32px)'}} onKeyDown={this.handleAdd}></Input>
                  </FormItem>
    
                  <FormItem>
                    <List bordered
                      pagination={{
                        onChange: (page) => {
                          console.log(page)
                        },
                        pageSize: 5,
                      }}
                      dataSource={allData}
                      renderItem={v => (
                        <ListItem style={{position: 'relative', paddingLeft: 22}}  key={v._id} onMouseEnter={() => (this.setState({showClose: v._id}))} onMouseLeave={() => (this.setState({showClose: -1}))}>
                          <Checkbox style={{position: 'absolute', top: 13, left: 0}} checked={v.checked} onChange={() => this.handleCheck(v._id, v.checked)}></Checkbox>
                          <ListItem.Meta description={v.text} title=''/>
                          {this.state.showClose === v._id ? <Icon type='close' style={{cursor: 'pointer', color: 'red'}} onClick={() => this.deleteItem(v._id)}/> : <Icon type='check' style={{color: 'rgb(240, 242, 245)'}}/>}
                        </ListItem>
                      )}
                    >
                    </List>
                  </FormItem>
    
                  <FormItem>
                    <span style={{float: 'left'}}>{leftNum} items left</span>
                    <span style={{float: 'left', marginLeft: '22%'}}>
                      <Link style={{margin: '0 25px', color: '#666'}} to='/all'>all</Link>
                      <Link style={{margin: '0 25px', color: '#666'}} to='/active'>active</Link>
                      <Link style={{margin: '0 25px', color: '#666'}} to='/completed'>completed</Link>
                    </span>
                    <span style={{float: 'right',cursor: 'pointer'}} onClick={this.clearData}>clear all</span>
                  </FormItem>
                </Form>
              </Col>
            </Row>
            <Row style={{minHeight: 50}}></Row>
          </div>
        )
      }
    }
    
    1. 附package.json
    {
      "private": true,
      "scripts": {
        "start": "roadhog server",
        "build": "roadhog build",
        "lint": "eslint --ext .js src test",
        "precommit": "npm run lint"
      },
      "dependencies": {
        "antd": "^3.6.6",
        "body-parser": "^1.18.3",
        "cookie-parser": "^1.4.3",
        "dva": "^2.3.1",
        "express": "^4.16.3",
        "mongoose": "^5.2.3",
        "react": "^16.2.0",
        "react-dom": "^16.2.0"
      },
      "devDependencies": {
        "babel-plugin-dva-hmr": "^0.3.2",
        "babel-plugin-import": "^1.8.0",
        "connect-multiparty": "^2.1.1",
        "dva-loading": "^2.0.3",
        "eslint": "^4.14.0",
        "eslint-config-umi": "^0.1.1",
        "eslint-plugin-flowtype": "^2.34.1",
        "eslint-plugin-import": "^2.6.0",
        "eslint-plugin-jsx-a11y": "^5.1.1",
        "eslint-plugin-react": "^7.1.0",
        "history": "^4.7.2",
        "husky": "^0.12.0",
        "qs": "^6.5.2",
        "redbox-react": "^1.4.3",
        "roadhog": "^2.0.0"
      }
    }
    

    相关文章

      网友评论

        本文标题:dva的入门和使用

        本文链接:https://www.haomeiwen.com/subject/yzqfmftx.html