dva入手指南

因為積分商城項目接觸dva搭建的項目,由于和以前使用vue框架不同,邊完成需求,邊學習框架,現對學習過程做一個記錄,希望對后來接觸dva的小伙伴有所幫助,有什么錯誤的地方,大家一起糾正。

first of all

照慣例介紹一下dva是什么

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

官方解釋是這樣,在我理解,dva是一個對react進行簡單封裝,更方便進行開發的react框架。


由于都是框架,我們大部分項目是vue進行的,可能小伙伴們對vue更加熟悉,在后續我也會根據vue進行對比,便于記憶。

1. 安裝

dva也使用腳手架進行安裝。

$ npm install dva-cli -g 

是不是很熟悉,和vue類似,用法也和vue-cli類似

2. 創建項目

$ dva new dva-quickstart

使用new

3. 啟動

$ cd dva-quickstart
$ npm start

本地就跑起來了

4. 安裝antd

在使用時我們搭配antd開發,antd為react的ui框架,有了ui框架開發起來更加方便快捷

$ npm install antd babel-plugin-import --save

注意,如果node版本高于5.4.0會出現報錯


Snipaste_2019-04-13_15-27-41.png

解決辦法:
1.node降版本
2.刪除node_modules文件夾

我是node5.6.0,所以

$ npm install antd babel-plugin-import --save
$ npm install

編輯 .webpackrc,使 babel-plugin-import 插件生效。

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

目錄結構

|- mock
|- node_modules
|- package.json
|- public
|- src
    |- asserts
    |- components
    |- models
    |- routes
    |- services
    |- utils
    |- router.js
    |- index.js
    |- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc
  • mock 存放用于 mock 數據的文件;
  • 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

和vue-cli生成的目錄結構類似,其中有幾個不同點強調一下

  1. model文件類似于vuex中store 用來控制狀態
  2. routes文件夾相當于常見的pages,當然,這里也可以改成pages
  3. 多了mock文件夾,如果只進行本地開發,可以用過.roadhogrc.mock.js配置數據進行本地開發
  4. dva開發組件都是return形式,不像vue是單寫html,這兩個區別比較大,需要熟悉

5.開始開發

我們通過vue對比進行開發,所以從我們熟悉的開始(routes已換成pages)

// .webpackrc.js
import { resolve } from 'path'
let publicPath
module.exports = {
    publicPath,
    outputPath: './dist',
    // 按需加載antd組件
    extraBabelPlugins: [
        [
            'import',
            {
                libraryName: 'antd',
                libraryDirectory: 'es',
                style: 'css',
            },
        ],
    ],
    alias: {
        components: resolve(__dirname, './src/components'),
        utils: resolve(__dirname, './src/utils'),
        pages: resolve(__dirname, './src/pages'),
        services: resolve(__dirname, './src/services'),
        api: resolve(__dirname, './src/api'),
        models: resolve(__dirname, './src/models'),
        img: resolve(__dirname, './src/assets/img'),
    },
}

1. 新增文件夾src/api將接口地址寫在里面
|- mock
|- node_modules
|- package.json
|- public
|- src
+   |- api
    |- asserts
    |- components
    |- models
    |- pages
    |- services
    |- utils
    |- router.js
    |- index.js
    |- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc

例如:

// src/api/commodity.js
// 商品列表
export const commodityListUrl = url
2. services 將所有后臺的接口請求都寫在里面,例如
// src/services/commodityService.js
import request from 'utils/request'
import {
    commodityListUrl,
} from 'api/commodity'
import { createAFordownLoad } from 'utils/methods'
/**
 * 獲取商品列表
 *
 * @param {*} payload
 * @returns
 */
export const getCommodityList = async payload => {
    try {
        const { result } = await request({
            url: `${commodityListUrl}`,
            data: payload,
        })
        return Promise.resolve(result)
    } catch (e) {
        return Promise.resolve(e.message)
    }
}
3. 添加路由
// src/router.js 注意這里是示意代碼 不是業務代碼,在實際開發中會根據情況變化
import React from 'react';
import { Router, Route } from 'dva/router';
import commodity from './pages/list';
// 假裝有其他
import example from './pages/example';

function RouterConfig({ history }) {
  return (
    <Router history={history}>
        <Route path="/" exact component={commodity} />
        <Route path="/example" exact component={example} />
    </Router>
  );
}

export default RouterConfig;

dva 內置了 dynamic 方法用于實現組件的動態加載,用法如下:

import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
  app,
  models: () => [
    import('./models/users'),
  ],
  component: () => import('./routes/UserPage'),
})

實際使用時,可以對其進行簡單的封裝,否則每個路由組件都這么寫一遍很麻煩。
例如我們可以:

import routes from '' // 把Route集中寫進一個文件
function RouterConfig({ history }) {
  return (
    <Router history={history}>
       <Switch>
            {routes.map(({ path, ...dynamics }, key) => (
                <Route
                    key={key}
                    path={path}
                    exact
                    component={dynamic({ app, ...dynamics })}
                />
            ))}
        </Switch>
    </Router>
  );
}

4. modal設計

Model 是 dva 最重要的部分,可以理解為 redux、react-redux、redux-saga 的封裝。
通常項目中一個模塊對應一個 model,一個基本的 model 如下:

import { hashHistory } from 'dva/router'
import { query } from '../services/users'
export default {
  namespace: 'commodity',

  state: {
    list: [],
  },

  subscriptions: {
    setup({ dispatch, history }) {
      // 進入頁面就調用
      history.listen(location => {
        if (location.pathname === '/') {
          dispatch({
            type: 'query',
            payload: {},
          })
        }
      })
    },
  },

  effects: {
    *query({ payload }, { select, call, put }) {
      yield put({ type: 'showLoading' })
      const { data } = yield call(query)
      if (data) {
        yield put({
          type: 'querySuccess',
          payload: {
            list: data.data,
            total: data.page.total,
            current: data.page.current,
          },
        })
      }
    },
    *create() {},
    // delete為關鍵字
    *'delete'() {},
    *update() {},
  },

  reducers: {
    showLoading(state, action) {
      return {
        ...state,
        loading: true,
      }
    },
    showModal() {},
    hideModal() {},
    querySuccess(state, action) {
      return {
        ...state,
        ...action.payload,
        loading: false,
      }
    },
    createSuccess() {},
    deleteSuccess() {},
    updateSuccess() {},
  },
}

  • namespace 是該 model 的命名空間,同時也是全局 state 上的一個屬性,只能是字符串,不支持使用 . 創建多層命名空間。
  • state 是狀態的初始值。
  • reducer 是唯一可以修改 state 的地方,由 action 觸發,它有 state 和 action 兩個參數。 類似vue中store的mutation
  • effects 用于處理異步操作,不能直接修改 state,由 action 觸發,也可觸發 action。它只能是 generator 函數,并且有 action 和 effects 兩個參數。第二個參數 effects 包含 put、call 和 select 三個字段,put 用于觸發 action,call 用于調用異步處理邏輯,select 用于從 state 中獲取數據。 類似vue中store中的action
  • subscriptions 用于訂閱某些數據源,并根據情況 dispatch 某些 action,格式為 ({ dispatch, history }, done) => unlistenFunction。
5. 組件設計(由于后臺項目比較大,建議寫在對應的頁面component里)
6. 連通組件和modal數據
|- src
    |- api
    |- asserts
    |- components
    |- models
    |- pages
+        |- commodity
+           |- component
+                |- dataTable.js
+           |- list.js
// src/pages/commodity/component/dataTable.js
import React, { Component } from 'react'
import { Table, Button } from 'antd' // antd 組件

class DataTable extends Component {
    render() {
        const {
            data: { list, pagination },
        } = this.props // 獲取數據
        const columns = [] // 定義表頭及內容
        return (
            <Table
                rowKey="draftSn"
                columns={columns}
                DataTable
                dataSource={list}
                pagination={pagination}
            />
        )
    }
}

export default DataTable
// src/pages/commodity/list.js
import React, { Component } from 'react'
import { connect } from 'dva'
import DataTable from './component/dataTable' // 注意組件一定要大寫

class CommodityList extends Component {
    state = {
        list: [],
    }
    render() {
        // 從modal獲取數據
        const {
            commodity, // modal數據
            dispatch, // service方法
            location, // 可以獲取url相關信息
            history,  // 路由
        } = this.props
        // 為組將傳參
        const tableProps = {
            data: commodity,
            dispatch,
            history,
        }
        return (
            <div>
                <DataTable {...tableProps} />
            </div>
        )
    }
}
// 連通modal和頁面數據
function mapStateToProps({ commodity }) {
    return { commodity }
}
export default connect(mapStateToProps)(CommodityList)

final

熟悉了這些,可以愉快開始開發了。雖然上手稍微比vue難一點,但是熟悉了,寫起來還是很爽的。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • dva.js 簡介 dva 是阿里前端架構師 sorrycc 帶 team 研發的一套輕量級前端框架,其目的是盡量...
    那顆星_fcaf閱讀 3,742評論 0 24
  • 前兩篇寫了react各種基本操作和主要概念但其實沒必要那么復雜直接用框架就好啦這年頭前端框架真是一睜眼就多出好幾個...
    saint37閱讀 25,819評論 9 45
  • 初始化 安裝 dva-cli 用于初始化項目: 創建項目目錄,并進入該目錄: 初始化項目: 然后運行 npm st...
    Paranoid_K閱讀 121,015評論 11 104
  • 接連目睹著身邊一對對標榜著“純潔異性關系”又經歷時間考驗的情侶開始公開戀愛,突然疑惑起來,究竟怎樣的關系更為純潔?...
    牧羊枕邊閱讀 191評論 0 1
  • 漸行漸遠的背影 在腦海里回旋 遠方 孤單的你 在等待著那一抹安寧與溫暖 我只愿化做魚木為你日夜祈禱
    殤葉為誰落閱讀 156評論 0 8