一步一步創(chuàng)建dva項(xiàng)目祟剔,完成用戶管理的 CURD 應(yīng)用 (react+dva+antd)(填坑優(yōu)化版)

前言

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)行了


image.png

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)在就可以切換路由了如下圖示:


222.gif

接下來著重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
最終完成效果如下圖所示:

222.gif

如遇問題歡迎加群討論
QQ群: 218618405
github:https://github.com/Sawyer-china

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匀油,一起剝皮案震驚了整個(gè)濱河市缘缚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敌蚜,老刑警劉巖忙灼,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钝侠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酸舍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門帅韧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啃勉,你說我怎么就攤上這事忽舟。” “怎么了淮阐?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵叮阅,是天一觀的道長。 經(jīng)常有香客問我泣特,道長浩姥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任状您,我火速辦了婚禮勒叠,結(jié)果婚禮上兜挨,老公的妹妹穿的比我還像新娘。我一直安慰自己眯分,他們只是感情好拌汇,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弊决,像睡著了一般噪舀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上飘诗,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天与倡,我揣著相機(jī)與錄音,去河邊找鬼疚察。 笑死蒸走,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的貌嫡。 我是一名探鬼主播比驻,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岛抄!你這毒婦竟也來了别惦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤夫椭,失蹤者是張志新(化名)和其女友劉穎掸掸,沒想到半個(gè)月后演训,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欺税,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年纯续,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仁讨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羽莺。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖洞豁,靈堂內(nèi)的尸體忽然破棺而出盐固,到底是詐尸還是另有隱情,我是刑警寧澤丈挟,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布刁卜,位于F島的核電站,受9級特大地震影響曙咽,放射性物質(zhì)發(fā)生泄漏蛔趴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一例朱、第九天 我趴在偏房一處隱蔽的房頂上張望夺脾。 院中可真熱鬧之拨,春花似錦、人聲如沸咧叭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菲茬。三九已至吉挣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婉弹,已是汗流浹背睬魂。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镀赌,地道東北人氯哮。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像商佛,于是被迫代替她去往敵國和親喉钢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容