- 创建项目
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)