创建项目
yarn create react-app project-name
覆盖 create-react-app webpack 配置, 不需要 eject
yarn add react-app-rewired customize-cra -D
修改 package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
创建 config-overrides.js
const {
override,
addLessLoader,
fixBabelImports,
addDecoratorsLegacy
} = require ( 'customize-cra' )
const modifyVars = require ( './theme' )
module. exports = override (
addLessLoader ( {
javascriptEnabled: true ,
modifyVars
} ) ,
addDecoratorsLegacy ( ) ,
fixBabelImports ( 'import' , {
libraryName: 'antd' ,
libraryDirectory: 'es' ,
style: true ,
} ) ,
)
添加 antd
yarn add antd
添加 less 支持
yarn add less less-loader -D
按需加载
yarn add babel-plugin-import -D
theme.js
module. exports = {
'@primary-color' : ' #1890ff' ,
'@link-color' : ' #1890ff' ,
'@success-color' : ' #52c41a' ,
'@warning-color' : ' #faad14' ,
'@error-color' : ' #f5222d' ,
'@font-size - base' : ' 14px' ,
'@heading-color' : ' rgba(0, 0, 0, 0.85)' ,
'@text-color' : ' rgba(0, 0, 0, 0.65)' ,
'@text-color - secondary ' : ' rgba(0, 0, 0, .45)' ,
'@disabled-color ' : ' rgba(0, 0, 0, .25)' ,
'@border-radius - base' : ' 4px' ,
'@border-color - base' : ' #d9d9d9' ,
'@box-shadow - base' : ' 0 2px 8px rgba(0, 0, 0, 0.15)' ,
}
装饰器模式支持
yarn add @babel/plugin-proposal-decorators -D
构建目录
mkdir src/components src/views src/routes src/reducers src/actions
添加 react-router
yarn add react-router-dom
添加基本视图页面
-views
-Home
-index.js
-Article
-index.js
-Edit.js
编写路由
import {
Dashboard,
Login,
NotFound,
} from '../views'
export const mainRouter = [
{
pathname: '/login' ,
component: Login
} ,
{
pathname: '/404' ,
component: NotFound
} ,
]
export const adminRouter = [
{
pathname: '/admin/dashboard' ,
component: Dashboard
} ,
在 index.js 里引入路由
import React from 'react'
import { render } from 'react-dom'
import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom'
import { mainRouter } from './routes'
import App from './App'
render (
< Router>
< Switch>
< Route path= '/admin' render= { routeProps => {
return < App { ... routeProps} / >
} } / >
{
mainRouter. map ( route => < Route
key= { route. pathname}
path= { route. pathname}
component= { route. component}
/ > )
}
< Redirect to= '/admin' from = '/' exact / >
< Redirect to= '/404' / >
< / Switch>
< / Router> ,
document. getElementById ( 'root' )
)
在主组件 App.js 里使用路由
import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import { adminRouter } from './routes'
const App = ( props) => (
< div>
< div> 公共部分< / div>
< Switch>
{
adminRouter. map ( route => (
< Route
key= { route. pathname}
exact= { route. exact}
path= { route. pathname}
render= { routerProps => (
< route. component
{ ... routerProps}
/ >
) }
/ >
) )
}
< Redirect to= { adminRouter[ 0 ] . pathname} from = '/admin' exact / >
< Redirect to= '/404' / >
< / Switch>
< / div>
)
export default App
添加延迟加载
yarn add react-loadable
编写 views/index.js 延迟加载组件
import { Loading } from '../components'
import Loadable from 'react-loadable'
const loadindComponent = ( loader, loading = Loading) => (
Loadable ( {
loader,
loading
} )
)
const Dashboard = loadindComponent (
( ) => import ( './Dashboard' ) ,
)
const Login = loadindComponent (
( ) => import ( './Login' ) ,
)
export {
Dashboard,
Login,
}
引入 antd 组件编写页面 添加 axios
yarn add axios
获取接口数据渲染页面
import Axios from "axios" ;
const isDev = process. env. NODE_ENV === 'development'
const ajax = Axios. create ( {
baseURL: isDev ? 'http://rap2api.taobao.org/app/mock/244403/' : ''
} )
ajax. interceptors. request. use ( ( config) => {
config. params = Object. assign ( { } , config. params, {
Authorization: 'Bearer ...'
} )
return config
} , err => {
console. log ( err)
} )
ajax. interceptors. response. use ( ( resp) => {
return resp. data. data
} , ( error) => {
if ( error. response) {
console. log ( error. response. data) ;
return Promise. reject ( error. response. data)
} else if ( error. request) {
console. log ( error. request) ;
} else {
console. log ( 'Error' , error. message) ;
}
return Promise. reject ( error)
} )
export const getArticles = ( offset = 0 , limit = 10 ) => {
return ajax. get ( '/api/v1/article' , { params: { offset, limit } } )
}
export const delArticles = ( id) => {
return ajax. delete ( '/api/v1/article/' + id)
}
添加 redux react-redux redux-thunk
yarn add redux react-redux redux-thunk
创建 src/store.js 作为全局存储
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
export default createStore (
rootReducer,
applyMiddleware ( thunk)
)
在 src/index.js 引入 store 并使用 Provider 高阶组件传递
import { Provider } from 'react-redux'
import store from './store'
< Provider store= { store} >
...
< / Provider>
创建 ./actions/actionType.js 定义类型
export default {
MARK_NOTIFICATION_AS_READ_BY_ID : 'MARK_NOTIFICATION_AS_READ_BY_ID' ,
...
GET_NOTIFICATIONS : 'GET_NOTIFICATIONS' ,
} ;
创建 ./actions/notifications.js 分发动作
import actionType from './actionTypes'
import { getNotifications } from '../requests'
export const markNotificationAsReadByid = ( id) => {
return dispatch => {
dispatch ( startNotification ( ) )
setTimeout ( ( ) => {
dispatch ( {
type: actionType. MARK_NOTIFICATION_AS_READ_BY_ID ,
payload: { id }
} )
dispatch ( endNotification ( ) )
} , 2000 )
}
}
...
export const endNotification = ( ) => {
return {
type: actionType. END_NOTIFICATION_LODING ,
}
}
export const notifications = ( ) => {
return dispatch => {
dispatch ( startNotification ( ) )
getNotifications ( ) . then ( ( resp) => {
dispatch ( {
type: actionType. GET_NOTIFICATIONS ,
payload: {
list: resp. list
}
} )
} ) . finally ( ( ) => {
dispatch ( endNotification ( ) )
} )
}
}
创建 ./reducers/index.js 合并多个 reducers
import { combineReducers } from 'redux'
import notifications from './notifications'
export default combineReducers ( {
notifications
} )
创建 ./reducers/notifications.js 定义 reducer 对不同的 actions 进行操作
import actionTypes from '../actions/actionTypes'
const initState = {
isLoading: true ,
list: [ ]
}
export default ( state = initState, action) => {
switch ( action. type) {
case actionTypes. START_NOTIFICATION_LODING :
return {
... state,
isLoading: true
}
case actionTypes. GET_NOTIFICATIONS :
return {
... state,
list: action. payload. list,
}
case actionTypes. MARK_NOTIFICATION_AS_READ_BY_ID :
return {
... state,
list: state. list. map ( item => {
if ( item. id === action. payload. id)
item. hasRead = true
return item
} )
}
...
case actionTypes. END_NOTIFICATION_LODING :
return {
... state,
isLoading: false
}
default :
return state
}
}
在页面使用获取全局属性
import { connect } from 'react-redux'
import { markNotificationAsReadByid, markAllNotificationsAsRead } from '../../actions/notifications'
const mapStateToProps = state => {
const {
list, isLoading
} = state. notifications
return {
list, isLoading
}
}
function Notification ( props) {
return (
< Spin spinning= { props. isLoading} >
< Card
title= '通知中心'
bordered= { false }
extra= {
< Button
disabled= { props. list. every ( item => item. hasRead === true ) }
onClick= { ( ) => {
props. markAllNotificationsAsRead ( )
} }
> 全部标记为已读< / Button>
}
>
< List
itemLayout= "horizontal"
dataSource= { props. list}
renderItem= { item => (
< List. Item
extra= {
item. hasRead ? null : < Button
onClick= { ( ) => {
props. markNotificationAsReadByid ( item. id)
} }
> 标记为已读< / Button>
}
>
< List. Item. Meta
avatar= { < Avatar src= "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" / > }
title= { < Badge dot= { ! item. hasRead} > { item. title} < / Badge> }
description= { item. desc}
/ >
< / List. Item>
) }
/ >
< / Card>
< / Spin>
)
}
export default connect ( mapStateToProps, { markNotificationAsReadByid, markAllNotificationsAsRead } ) ( Notification)