前言
dva 官方項(xiàng)目里面有些用法不是最新的限次,本文針對性的解決并以正確的用法告知剛開始接觸dva 項(xiàng)目的碼友們 芒涡,幫助大家避免入坑。
12 步 30 分鐘卖漫,完成用戶管理的 CURD 應(yīng)用 優(yōu)化版
git地址:https://github.com/Sawyer-china/react-user-dashboard
現(xiàn)在我們就開始一步一步的構(gòu)建 如遇見問題可 添加 qq群:218618405 進(jìn)行提問
如果你覺得該文章對你有幫助加個(gè)喜歡费尽,github 加個(gè) start 謝謝
1. 安裝全局 dva-cli
$ npm install dva-cli -g
2. 創(chuàng)建項(xiàng)目
選好目錄然后創(chuàng)建項(xiàng)目
$ dva new user-dashboard
3. 根據(jù)提示進(jìn)入目錄并運(yùn)行項(xiàng)目
$ cd user-dashboard
$ npm start
如果看到以下頁面,那么恭喜你羊始,我們往下進(jìn)行了
4. 引入antd組件庫 (andt: 官方地址 https://ant.design/index-cn)
$ npm install antd --save
安裝完成后打開 src/routes/IndexPage.js 引入一個(gè)antd組件試試 在文件頭部引入
import Button from 'antd/lib/button'
import 'antd/lib/button/style/css';
在function IndexPage 函數(shù)中使用組件
<Button type="primary">Primary</Button>
如果頁面出現(xiàn)一個(gè)Button 則代表成功了
5. 按需加載
在項(xiàng)目中往往我們需要進(jìn)行組件的按需加載以免去不必要的組件被打包到實(shí)際的項(xiàng)目中旱幼,以減少js的體積大小
安裝以下插件
$ cnpm install babel-plugin-import --save-dev
并找到根目錄下面的.webpackrc文件,并在文件中添加插件配置
"extraBabelPlugins": [
["import", { "libraryName": "antd", "style": "css" }]
]
配置更多玩法參考:https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md
修改以下代碼
// import Button from 'antd/lib/button'
// import 'antd/lib/button/style/css';
import { Button } from 'antd'
6. 做 webpack 反向代理
在配置文件中添加以下代碼
"proxy": {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api": "" }
}
}
訪問 http://localhost:8000/api/users 如果你看見一串json數(shù)據(jù)代表代理成功突委,就可以進(jìn)行下一步開發(fā)了(該json數(shù)據(jù)是dva官方提供的測試數(shù)據(jù)柏卤,使用Mockjs開發(fā))
完成以上準(zhǔn)備工作我們就開始正式的demo開發(fā)了
7. 創(chuàng)建 Users.js Router
在routes目錄下創(chuàng)建Users.js
import React, { Component } from 'react'
import { connect } from 'dva'
import styles from './Users.css'
class Users extends Component {
render() {
return (
<div className={styles.normal}>
Users.js
</div>
)
}
}
Users.propsTypes = {}
export default connect()(Users)
8. 配置路由 打開根目錄router.js
import React from 'react'
import { Router, Route, Switch, Redirect, routerRedux } from 'dva/router'
import IndexPage from './routes/IndexPage'
import dynamic from 'dva/dynamic' // 路由按需加載
const { ConnectedRouter } = routerRedux
function RouterConfig({ history, app }) {
const IndexPage = dynamic({
app,
component: () => import('./routes/IndexPage')
})
const Users = dynamic({
app,
component: () => import('./routes/Users')
})
return (
<ConnectedRouter history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
<Route path="/users" exact component={Users} />
</Switch>
</ConnectedRouter>
)
}
export default RouterConfig
瀏覽地址 輸入 http://localhost:8000/#/users 將會看到 users 路由頁面
9. 在components 文件夾下 新建 MainLayout/Header.js
import React, { Component } from 'react'
import { Menu, Icon } from 'antd'
import { connect } from 'dva'
import { Link, routerRedux } from 'dva/router'
class Header extends Component {
render() {
const { location } = this.props
return (
<Menu
selectedKeys={[location.pathname]}
mode="horizontal"
theme="dark"
>
<Menu.Item key="/users">
<Link to="/users">
<Icon type="bars" />Users
</Link>
</Menu.Item>
<Menu.Item key="/">
<Link to="/">
<Icon type="home" />Home
</Link>
</Menu.Item>
<Menu.Item key="/404">
<Link to="/page-you-dont-know">
<Icon type="frown-circle" />404
</Link>
</Menu.Item>
<Menu.Item key="/antd">
<a >dva</a>
</Menu.Item>
</Menu>
)
}
}
export default connect()(Header)
10. 在 components/MainLayout 新建MainLayout.js
import React, { Component } from 'react'
import styles from './MainLayout.css'
import Header from './Header'
class MainLayout extends Component {
render() {
const { children, location } = this.props
return (
<div className={styles.normal}>
<Header />
<div className={styles.content}>
<div className={styles.main}>
{children}
</div>
</div>
</div>
)
}
}
export default MainLayout
11. 在 routes 中添加 App.js
import React, { Component } from 'react'
import { connect } from 'dva'
import { withRouter } from 'dva/router'
import MainLayout from '../components/MainLayout/MainLayout'
class App extends Component {
render() {
let { children, location } = this.props
return (
<MainLayout location={location}>
{children}
</MainLayout>
)
}
}
App.propTypes = {}
export default withRouter(
connect(({ app, loading }) => ({
app,
loading
}))(App)
)
添加完成之后修改 router.js 頁面
在頭部引入 App.js
import App from './routes/App'
然后修改return 中的代碼
return (
<ConnectedRouter history={history}>
<App>
<Switch>
<Route path="/" exact component={IndexPage} />
<Route path="/users" exact component={Users} />
<Route path="*" render={() => <Redirect to="users" />} />
</Switch>
</App>
</ConnectedRouter>
)
現(xiàn)在就可以切換路由了如下圖示:
接下來著重users頁面的開發(fā)
12. 創(chuàng)建 users model 和 service
新建 src/models/users.js
:
// user api
import * as usersService from '../services/users'
// 引入 node 模塊
// import url from 'url'
// import qs from 'qs'
export default {
namespace: 'users',
state: {
list: [],
total: 0,
page: 0
},
reducers: {
/**
* test
* @param {*} state
* @param {*} param1
*/
save(state, { payload: { data: list, total, page } }) {
return { ...state, list, total, page }
},
search(state) {
return { ...state }
}
},
effects: {
*fetch({ payload: { page } }, { call, put }) {
const { data, headers } = yield call(usersService.fetch, { page })
yield put({
type: 'save',
payload: {
data,
total: headers['x-total-count'],
page: parseInt(page, 10)
}
})
},
*create({ payload: values }, { call, put }) {
yield call(usersService.create, values)
},
*patch({ payload: { id, values } }, { call, put }) {
yield call(usersService.patch, { id, values })
yield put({ type: 'reload' })
},
*remove({ payload: { id } }, { call, put }) {
yield call(usersService.remove, { id })
yield put({ type: 'reload' })
},
*reload(action, { put, select }) {
const page = yield select(state => state.users.page)
yield put({ type: 'fetch', payload: { page } })
}
},
subscriptions: {
// setup({ dispatch }, done) {
// done('錯(cuò)了錯(cuò)了')
// throw new Error('Whoops!')
// }
setup({ dispatch, history }) {
return history.listen(({ pathname, search }) => {
// const { query } = url.parse(search)
// const oPath = qs.parse(query)
// if (pathname === '/users') {
// console.log('/users')
// console.log(oPath)
// dispatch({ type: 'fetch', payload: oPath })
// }
})
}
}
}
新建 src/services/users.js
import request from '../utils/request'
export function queryUsers() {
return request('/api/users')
}
export function fetch({ page = 1 }) {
return request(`/api/users?_page=${page}&_limit=5`)
}
export function create(values) {
return request('/api/users', {
methods: 'POST',
data: JSON.stringify(values)
})
}
export function patch({ id, values }) {
return request(`/api/users/${id}`, {
methods: 'PATCH',
data: JSON.stringify(values)
})
}
export function remove({ id }) {
return request(`/api/users/${id}`, {
methods: 'DELETE'
})
}
由于我們需要從 response headers 中獲取 total users 數(shù)量,所以需要改造下 src/utils/request.js:
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) {
// return fetch(url, options)
// .then(checkStatus)
// .then(parseJSON)
// .then(data => ({ data }))
// .catch(err => ({ err }));
// }
async function request(url, options) {
const response = await fetch(url, options)
checkStatus(response)
const data = await response.json()
const ret = {
data,
headers: {}
}
if (response.headers.get('x-total-count')) {
ret.headers['x-total-count'] = response.headers.get('x-total-count')
}
return ret
}
export default request
剩余部分請參考
https://github.com/sorrycc/blog/issues/18 (該代碼可能有部分內(nèi)容會導(dǎo)致錯(cuò)誤)
請以一下鏈接為準(zhǔn)
https://github.com/Sawyer-china/react-user-dashboard
最終完成效果如下圖所示:
如遇問題歡迎加群討論
QQ群: 218618405
github:https://github.com/Sawyer-china