从 0 配置 React 全家桶

本文总结一下 React + React Router + Redux + Ant Design 这套技术栈配合使用的一些要点

实现异步组件

异步组件可以让组件被使用时,也就是路由有所匹配时才进行加载,其资源也会被单独打包,代码也会被分割,能够有效减少首屏渲染时间

  • 创建实现异步组件的高阶组件

通过 import 进来的组件都被包裹进了 default 属性里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react'

function asyncComponent(importComponent) {
class AsyncComponent extends React.Component {
state = {
component: null
}

async componentDidMount() {
const { default: Component } = await importComponent()
this.setState({
component: <Component {...this.props} />
})
}

render() {
return this.state.component
}
}
return AsyncComponent
}

export default asyncComponent
  • 使用异步组件时用 import 传入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import AsyncComponent from './utils/AsyncComponent'

const AsyncRegister = AsyncComponent(() => import('./components/Register'))

class Routes extends Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/register" component={AsyncRegister} />
</Switch>
</Router>
)
}
}

export default Routes
  • 打包时代码就被 webpack 自动切分成小块了

React 16.6 之后可以用过 lazy 方法和 Suspense 组件实现异步组件

使用 Ant Design

  • 首先是要配置按需加载和支持less

https://ant.design/docs/react/use-with-create-react-app-cn

  • 其次,它的默认语言是英语,改成中文可以:

https://ant.design/components/locale-provider-cn/

  • 最后在使用的过程中,多次用到的组件应该抽取成公共组件

使用 react-router-dom

  • 可以创建一个 router 组件作为入口组件,这个组件就相当于路由表

  • 子路由可以直接嵌套,也可以通过 render 的方式返回

1
$ yarn add react-router-dom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import RequireAuth from './utils/RequireAuth'
import ManageAuth from './utils/ManageAuth'
import Login from './components/Login'
import AsyncComponent from './utils/AsyncComponent'

const AsyncRegister = AsyncComponent(() => import('./components/Register'))
const AsyncAdmin = AsyncComponent(() => import('./components/Admin'))
const AsyncNoMatch = AsyncComponent(() => import('./components/NoMatch'))

class Routes extends Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={AsyncRegister} />
<AsyncAdmin>
<Switch>
<Route exact path="/Record" component={RequireAuth(AsyncRecord)} />
<Route component={AsyncNoMatch} />
</Switch>
</AsyncAdmin>
</Switch>
</Router>
)
}
}

export default Routes

使用 Redux

  • 安装 redux
1
$ yarn add redux react-redux
  • 安装 redux 处理异步的中间件和 redux-dev-tools
1
$ yarn add redux-thunk redux-devtools-extension
  • configureStore.js
1
2
3
4
5
6
7
8
9
10
import { createStore, applyMiddleware } from 'redux'
import rootReducer from '../reducers'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'

export default function configureStore() {
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))

return store
}

更多配置详见 redux-devtools-extension 文档

  • 拆分 Rducer

可以在 reducer 文件夹的入口文件里通过 combineReducers 将多个 reducer 组合起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { combineReducers } from 'redux'
import user from './user'
import switchMenu from './switchMenu'
import room from './room'
import manage from './manage'
import record from './record'

const rootReducer = combineReducers({
user,
switchMenu,
room,
manage,
record
})

export default rootReducer

配置 hmr

hmr 即热模块加载,可以让 create-react-app 在不刷新网页的情况下重新渲染页面。

  • index.js
1
2
3
4
5
6
7
8
9
10
if (module.hot) {
module.hot.accept('./<入口文件路径>', () => {
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.getElementById('root')
)
})
}
  • configureStore.js
1
2
3
4
5
6
7
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
module.hot.accept('../reducers', () => {
store.replaceReducer(rootReducer)
})
}
}

递归渲染菜单

  • 定义菜单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const menuList = [
{
name: 'xxx',
route: '/',
icon: 'xxx'
},
{
name: 'xxx',
route: '/xxx',
icon: 'xxx',
children: [
{
name: 'xxx',
route: '/xx/xxx',
icon: 'xxx'
},
{
name: 'xxx',
route: '/xx/xxx',
icon: 'xxx'
}
]
}
]
export default menuList
  • 递归渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// method
renderMenu = data => {
return data.map(item => {
if (item.children) {
return (
<SubMenu
title={
<span>
{item.icon && <Icon type={item.icon} />}
<span>{item.name}</span>
</span>
}
key={item.route}
>
{this.renderMenu(item.children)}
</SubMenu>
)
}
return (
<Menu.Item key={item.route} title={item.name}>
<NavLink to={item.route}>
{item.icon && <Icon type={item.icon} />}
<span>{item.name}</span>
</NavLink>
</Menu.Item>
)
})
}

// jsx
{
this.renderMenu(menuList)
}

使用 props.children 组织页面布局

  • Layout 组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Admin extends React.Component {
render() {
return (
<Layout>
<Sider />
<Layout>
<Header />
<Content>{this.props.children}</Content>
<Footer />
</Layout>
</Layout>
)
}
}
  • routes 组件

Amdin 包裹的内容都会被显示到 this.props.children

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
................

class Routes extends React.Component {

render() {
return (
<Router>
...........
<Admin>
<Route exact path="/" component={Dashboard} />
...............
</Admin>
</Router>
)
}
}

export default Routes

高阶组件保护路由

很多情况下组件在被访问之前需要做一层逻辑判断,如果不符合条件就禁止访问,这时就可以在高阶组件中处理这部分逻辑。

  • 创建高阶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { Component } from 'react'
import { message } from 'antd'
import { connect } from 'react-redux'

export default function(ConposedConponent) {
class RequireAuth extends Component {
componentDidMount() {
if (this.props.isLogin === false) {
message.warning('请先登录!')
this.props.history.push('/login')
}
}

render() {
return <ConposedConponent {...this.props} />
}
}

const mapStateToProps = state => {
return {
isLogin: state.user.isLogin
}
}

return connect(mapStateToProps)(RequireAuth)
}
  • router.js

使用时将受保护的组件用告诫组件包裹起来

1
<Route exact path="/manage" component={RequireAuth(AsyncManage)} />
0%