因為積分商城專案接觸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會出現報錯
解決辦法: 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生成的目錄結構類似,其中有幾個不同點強調一下
- model檔案類似於vuex中store 用來控制狀態
- routes資料夾相當於常見的pages,當然,這裡也可以改成pages
- 多了mock資料夾,如果只進行本地開發,可以用過.roadhogrc.mock.js配置資料進行本地開發
- 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)
複製程式碼