生命周期(新旧对比)
旧版生命周期
旧版生命周期 指的是 React 16.3 及其之前的版本。
新版生命周期
static getDerivedStateFromProps
-
static getDerivedStateFromProps(nextProps,prevState)
:接收父组件传递过来的props
和组件之前的状态,返回一个对象来更新state
或者返回null
来表示接收到的props
没有变化,不需要更新 state. -
该生命周期钩子的作用: 将父组件传递过来的
props
映射 到子组件的state
上面,这样组件内部就不用再通过this.props.xxx
获取属性值了,统一通过this.state.xxx
获取。映射就相当于拷贝了一份父组件传过来的props
,作为子组件自己的状态。注意:子组件通过setState
更新自身状态时,不会改变父组件的props
-
配合
componentDidUpdate
,可以覆盖componentWillReceiveProps
的所有用法 -
该生命周期钩子触发的时机:
- 在 React 16.3.0 版本中:在组件实例化、接收到新的
props
时会被调用 - 在 React 16.4.0 版本中:在组件实例化、接收到新的
props
、组件状态更新时会被调用
- 在 React 16.3.0 版本中:在组件实例化、接收到新的
// 根据新的属性对象派生状态对象
// nextProps:新的属性对象 prevState:旧的状态对象
static getDerivedStateFromProps(nextprops, state) {
console.log('props',nextprops);
// 返回一个对象来更新 state 或者返回 null 来表示接收到的 props 不需要更新 state
if (nextProps.parentName !== 'parent info') {
return {
info: nextprops.parentName,
// 注意:这里不需要把组件自身的状态也放进来
};
}
return null;
}
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps
,prevState
):接收父组件传递过来的props
和组件之前的状态,此生命周期钩子必须有返回值,返回值将作为第三个参数传递给componentDidUpdate
。必须和componentDidUpdate
一起使用,否则会报错。- 该生命周期钩子触发的时机 :被调用于
render
之后、更新DOM
和refs
之前 - 该生命周期钩子的作用: 它能让你在组件更新
DOM
和refs
之前,从DOM
中捕获一些信息(例如滚动位置) - 配合
componentDidUpdate
, 可以覆盖componentWillUpdate
的所有用法 - demo:每次组件更新时,都去获取之前的滚动位置,让组件保持在之前的滚动位置
getSnapshotBeforeUpdate() {
// 返回更新内容的高度
return this.wrapper.current.scrollHeight;
}
componentDidUpdate(prevProps, prevState, prevScrollHeight) {// 在这里拿到高度
this.wrapper.current.scrollTop =
this.wrapper.current.scrollTop +
(this.wrapper.current.scrollHeight - prevScrollHeight);
}
版本迁移
-
componentWillMount
,componentWillReceiveProps
,componentWillUpdate
这三个生命周期因为经常会被误解和滥用,所以被称为 不安全(不是指安全性,而是表示使用这些生命周期的代码,有可能在未来的React
版本中存在缺陷,可能会影响未来的异步渲染) 的生命周期 -
React 16.3 版本:为不安全的生命周期引入别名
UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
。(旧的生命周期名称和新的别名都可以在此版本中使用) -
React 16.3 之后的版本:为
componentWillMount
,componentWillReceiveProps
和componentWillUpdate
启用弃用警告。(旧的生命周期名称和新的别名都可以在此版本中使用,但旧名称会记录 DEV 模式警告)
性能优化
我们都知道,react 是数据驱动视图的变化,即是通过reder
来渲染视图,当数据(即状态)变化时,我们的页面就应当重新渲染。但是应用复杂之后就会出现这种情况:一个父组件 A 下面包含了多个子组件 B、C、D。假如 B、C 组件用到了父组件 A 的某个属性,子组件 D 却没有用到这个属性,当父组件的这个属性改变的时候,他下面的子组件 B、C 组件重新渲染,但是子组件 D 本不需要重新渲染,但是他没办法,他也被重新渲染了。这就造成了性能浪费了。说这么多,不如我们来看个例子:
// 父组件
import React, { Component } from 'react'
import { Button } from 'antd'
import Son1 from './son1'
import Son2 from './son2'
import Son3 from './son3'
interface Istate {
info1: string
info2: string
}
export class Parent extends Component<Istate> {
state: Istate = {
info1: 'info1',
info2: 'info2',
}
info1Change = () => {
this.setState({
info1: 'info1被改变了...',
})
}
render() {
return (
<div>
<p>父组件</p>
<Button onClick={this.info1Change}> 点击更改info1</Button>
<Son1 info1={this.state.info1} />
<Son2 info2={this.state.info2} />
</div>
)
}
}
export default Parent
// 子组件1
import React, { Component } from 'react'
interface Iprops {
info1: string
}
class Son1 extends Component<Iprops> {
render() {
console.log('son1重新渲染了....')
return (
<div>
<p>我是son1</p>
<p>{this.props.info1}</p>
</div>
)
}
}
export default Son1
// 子组件2
import React, { Component } from 'react'
interface Iprops {
info2: string
}
class Son2 extends Component<Iprops> {
render() {
console.log('son2重新渲染了....')
return (
<div>
<p>我是son2</p>
<p>{this.props.info2}</p>
</div>
)
}
}
export default Son2
上面这个例子,父组件提供了两个值:info1
和 info2
,其中 Son1
组件只用到了 info1
,Son2
组件只用到了 info2
。我们在父组件中,点击了按钮改变了 info1
的值,父组件必须重新渲染,因为它自身状态改变了,Son1
也应该重新渲染,因为它依赖于 info1
,而 Son2
是否应该重新渲染呢?按道理,它不应该重新渲染,因为 info2
没有改变,但是当我们每次点击按钮改变 info1
的时候,Son1
和Son2
都重新渲染了,这就明显存在问题了。
shouldComponentUpdate
在上面 👆 生命周期章节,我们讲到了shouldComponentUpdate
这个生命周期钩子,它接收两个参数,一个是下一次的 props
和下一次的 state
,在这里,我们拿到下一次的 props
(nextProps
)和当前的 props
进行比较,根据我们的场景,返回一个 bool
变量,返回 true
,则表示要更新当前组件,返回 false
则表示不更新当前组件。
import React, { Component } from 'react'
interface Iprops {
info2: string
}
class Son2 extends Component<Iprops> {
// 利用生命周期 shouldComponentUpdate 进行比较
shouldComponentUpdate(nextProps: Iprops, nextState: any) {
if (nextProps.info2 === this.props.info2) return false
return true
}
render() {
console.log('son2重新渲染了....')
return (
<div>
<p>我是son2</p>
<p>{this.props.info2}</p>
</div>
)
}
}
export default Son2
当我们再次点击按钮更改info1
的值,发现Son2
就不会再重新渲染了。
PureComponet
react
为我们提供了PureComponet
的语法糖,用它也可以用作组件是否渲染的比较。它的原理就是内部实现了shouldComponentUpdate
。让我们用PureComponet
来改造一下刚刚的Son2
组件:
import React, { PureComponent } from 'react'
interface Iprops {
info2: string
}
class Son2 extends PureComponent<Iprops> {
render() {
console.log('son2重新渲染了....')
return (
<div>
<p>我是son2</p>
<p>{this.props.info2}</p>
</div>
)
}
}
export default Son2
再次点击按钮改变info1
的值,发现Son2
也不会渲染了。
虽然PureComponent
帮我们很好的实现了shouldComponentUpdate
,但是它也是有缺点的。它只能用作对象的浅层比较,也就是它只会进行一层比较,当我们的数据是嵌套的对象或者数组的时候,它就无法比较了。所以PureComponent
最好只用于展示型组件
除了以上缺点以外,PureComponent
还有一些另外值得我们注意的地方:
当我们给PureComponent
包裹的子组件传入一个立即执行函数
的时候,父组件的状态改变的时候,这个子组件始终会重新渲染:
<Son2 info2={this.state.info2} change={() => {}} />
这个问题的出现是因为 change
这个函数每次都会执行,所以导致 Son2
组件每次都会重新渲染。这个问题的解决方法很简单,有两种方法:
- 第一种,将这个立即执行函数,抽取到类方法上,并且在
constructor
bindthis
:
constructor(props: any) {
super(props)
this.change = this.change.bind(this)
}
state: Istate = {
info1: 'info1',
info2: 'info2',
}
info1Change = () => {
this.setState({
info1: 'info1被改变了...',
})
}
change() {}
render() {
return (
<div>
<p>父组件</p>
<Button onClick={this.info1Change}> 点击更改info1</Button>
<Son1 info1={this.state.info1} />
<Son2 info2={this.state.info2} change={this.change} />
</div>
)
}
- 第二种,利用箭头函数将立即函数抽取成类属性:
state: Istate = {
info1: 'info1',
info2: 'info2',
}
info1Change = () => {
this.setState({
info1: 'info1被改变了...',
})
}
change = () => {}
render() {
return (
<div>
<p>父组件</p>
<Button onClick={this.info1Change}> 点击更改info1</Button>
<Son1 info1={this.state.info1} />
<Son2 info2={this.state.info2} change={this.change} />
</div>
)
}
Memo
刚刚我们介绍了PureComponent
,但是这只是用于class
组件,当我们用函数组件时,react
也给我们提供了一种方式:memo
import React, { memo } from 'react'
interface Iprops {
info2: string
}
const Son3: React.FC<Iprops> = (props) => {
console.log('son3重新渲染了....')
return (
<div>
<p>我是Son3</p>
<p>{props.info2}</p>
</div>
)
}
export default memo(Son3)
不过使用 memo 的时候也有PureComponent
的限制,我们仍然需要注意。
hooks
随着react 16.8
版本的出现,hooks
也问世了。hooks
解决了class
组件饱受诟病的众多问题,比如绑定this
的问题、组件的逻辑复用的问题等等。其实起初我对hooks
并不十分感冒,因为那个时候笔者连class
写法都还没掌握,再学个这玩意简直徒增烦恼。后来没办法,团队里的小伙伴都开始使用hooks
,所以我也被动学习了一波。这不写不知道,一写就真香!!!所以赶紧学起来吧
useState
记住一个点,useState
返回两个参数,一个是state
(也就是我们的state
)、一个是用于更新state
的函数。其实你叫啥名都行,不过咱们为了给 hooks 一个标志,大多采用如下写法:
const [name, setName] = useState(initState)
好了,掌握这个就行了,我们来使用一下:(别忘记了,我们现在只需要写函数式组件了哦)
import React, { useState } from 'react'
import { Button } from 'antd'
const Home: React.FC<Iprops> = ({ dispatch, goodsList }) => {
const [info, setInfo] = useState('init info')
return (
<div>
<p>{info}</p>
<Button onClick={() => setinfo('改变info')}> 点击更改info</Button>
</div>
)
}
export default Home
当我们初次进入 home
页面时,页面上会显示 info
的初始值:init info
,然后当我们点击按钮,调用 setInfo
,然后 info
的值就被改变了。就是这么简单。
useEffect
useEffect
其实只比复杂了那么一点。它合成了 calss
组件中的componentDidMount
、componentDidUpdate
、 componentWillUnmount
。 我们很容易就明白,它是用来执行副作用的,最最常见的副作用就是异步请求。
下面我们用它来实现class
组件的componentDidMount
的用法:
const Home: React.FC<Iprops> = ({ dispatch, goodsList }) => {
// 获取商品列表
const getList = () => {
dispatch({
type: `${namespace}/getGoodsList`,
})
}
useEffect(() => {
getList() // 调用方法发起异步请求
}, [])
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }}>
{goodsList.map((item, index) => {
return <Card hoverable style={{ width: 240 }} cover={<img alt='example' src={item} />}></Card>
})}
</div>
</div>
)
}
const mapStateToProps = (model) => ({
goodsList: model[namespace].goodsList,
})
export default connect(mapStateToProps)(Home)
上面的getList
就是咱们发起异步请求的方法,我们在useEffect
里面使用了它,同时我们还传入了一个[]
,就表示我们只需在页面初始化的时候发起请求,这样使用就相当于class
组件的componentDidMount
。
接下来我们再来实现class
组件的componentDidUpdate
的用法:
import React, { useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { Button, Card } from 'antd'
import { namespace } from '../models/model1'
interface Iprops {
goodsList: any[]
dispatch: any
}
const Home: React.FC<Iprops> = ({ dispatch, goodsList }) => {
const [info, setInfo] = useState('init info')
// 获取商品列表
const getList = () => {
dispatch({
type: `${namespace}/getGoodsList`,
})
}
useEffect(() => {
getList()
}, [info])
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<p>我是home页</p>
<p>{info}</p>
<Button onClick={() => setInfo('改变info')}> 点击更改info</Button>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{goodsList.map((item, index) => {
return <Card hoverable style={{ width: 240 }} cover={<img alt='example' src={item} />}></Card>
})}
</div>
</div>
)
}
const mapStateToProps = (model) => ({
goodsList: model[namespace].goodsList,
})
export default connect(mapStateToProps)(Home)
看上面,我们希望点击按钮时改变 info
时,它会自动再去发起请求,从而刷新页面(也就是说,goodsList
的数据依赖于 info
)。可以看见,我们这里还是利用了useEffect
的第二个参数,只不过这次我们传入的是[info]
,意思就是告诉useEffect
,如果 info
的值发生改变了,就去发起请求。这相当于我们在class
组件的componentDidMount
钩子。
接下来还有最后一个class
组件的componentWillUnmount
的用法了。这个就更简单了,我们只需要在useEffect
return
一个回调函数,就可以用来清除上一次副作用留下的副作用了:
....
useEffect(() => {
getList()
return () => dispatch({ type: `${namespace}/clearData` })
}, [])
....
useRef
这个hook
更简单了,它就是用来拿到子组件的实例的,相当于class
组件的React.createRef()
:
import React, { useState, useEffect, useRef } from 'react'
import { connect } from 'react-redux'
import { Button, Card } from 'antd'
import { namespace } from '../models/model1'
import Son from './components/son'
interface Iprops {
goodsList: any[]
dispatch: any
}
const Home: React.FC<Iprops> = ({ dispatch, goodsList }) => {
const sonRef = useRef(null) // 在这里新建一个子组件的ref
const [info, setInfo] = useState('init info')
// 获取商品列表
const getList = () => {
conson.log(sonRef.current) // 在这里就可以通过sonRef拿到子组件
dispatch({
type: `${namespace}/getGoodsList`,
})
}
useEffect(() => {
getList()
}, [info])
return (
<div>
<p>我是home页</p>
<p>{info}</p>
<Button onClick={() => setInfo('改变info')}> 点击更改info</Button>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{goodsList.map((item, index) => {
return <Card hoverable style={{ width: 240 }} cover={<img alt='example' src={item} />}></Card>
})}
</div>
<Son />
</div>
)
}
const mapStateToProps = (model) => ({
goodsList: model[namespace].goodsList,
})
export default connect(mapStateToProps)(Home)
useContext
useContext
这个hook
的作用也很简单,它可以让我们在函数组件中使用Context
,而且它还解决了以前我们需要利用Consumer
包裹组件的问题:
// context.js
import React from 'react'
const { Provider, Consumer } = React.createContext(null) //创建 context 并暴露Provider和Consumer
export { Provider, Consumer }
// 父组件
import React from 'react'
import Son from './son'
import { Provider } from './context'
class Father extends React.Component {
constructor(props) {
super(props)
}
state = {
info: 'info from father',
}
render() {
return (
<Provider value={this.state.info}>
<div>
<Son />
</div>
</Provider>
)
}
}
export default Father
在 class
组件里面,我们要想拿到 Context 里面的值,必须通过 Consumer
包裹组件:
// 子组件
import React from 'react'
import { Consumer } from './context'
class Son extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<Consumer>
{(info) => (
// 通过Consumer直接获取父组件的值
<div>
<p>父组件的值:{info}</p>
</div>
)}
</Consumer>
)
}
}
export default Son
有了 useContext
,就只需要这样:
// 子组件
import React from 'react'
funcion Son() {
const info = useContext(Context)
render() {
return (
<p>父组件的值:{info}</p>
)
}
}
export default Son
我们可以看到上面直接使用 React.useContext(Context)
就可以获得 context
,而在之前的版本中需要像这样才能获取 <Consumer>({vlaue} => {})</Consumer>
,这极大的简化了代码的书写。
useMemo
先来看看官网给出的用法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
根据官网的解释和这个用法可以看出,在 a
和 b
的变量值不变的情况下,memoizedValue
的值不变。即是:useMemo
函数的第一个入参函数不会被执行,从而达到节省计算量的目的(有点像vue
的计算属性)。那它有什么用呢?通常来说可以用作性能优化的手段。我们来看一个例子:
// 父组件
import React, { useState } from 'react'
import { Input } from 'antd'
import Son1 from './son1'
interface Iprops {}
const Home: React.FC<Iprops> = () => {
const [info, setInfo] = useState('')
const [visible, setVisible] = useState(true)
const onVisible = () => {
setVisible((visible) => !visible)
}
const changeInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setInfo(value)
}
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<p>{info}</p>
<Input onChange={(e) => changeInfo(e)}></Input>
<Son1 onVisible={onVisible} />
</div>
)
}
export default Home
// 子组件
import React from 'react'
import { Button } from 'antd'
interface Iprops {
onVisible: () => void
}
const Son1: React.FC<Iprops> = ({ onVisible }) => {
console.log('我被重新渲染了....')
return (
<div>
<Button onClick={() => onVisible()}>button</Button>
</div>
)
}
export default Son1
在父组件中,有个Input
输入框,每次输入新的值,父组件的info
的值就会发生改变,同时我们发现子组件每次都会重新渲染,即使我们子组件没用到info
的值,那是因为setInfo
导致父组件重新渲染了,也导致onVisible
每次都变成一个新的值,所以引起子组件重新渲染。那么有的同学就会说,可以利用React.memo
,我们来试一试:
import React, { memo } from 'react'
import { Button } from 'antd'
interface Iprops {
onVisible: () => void
}
const Son1: React.FC<Iprops> = ({ onVisible }) => {
console.log('我被重新渲染了....')
return (
<div>
<Button onClick={() => onVisible()}>button</Button>
</div>
)
}
export default memo(Son1)
然后我们随便在输入框输入新的值,我们发现,子组件仍然会重新渲染,为什么呢?那是因为这里的props.onVisible
是一个函数,它是一个引用类型的值,当父组件重新渲染onVisible
这个函数也会重新生成,这样引用地址变化就导致对比出新的数据,子组件就会重新渲染。所以我们需要缓存onVisible
这个函数,即是:我们只需要创建一遍这个函数,以后父组件重新渲染的时候,onVisible
的值仍然是第一次渲染的值,这样子组件才不会重新渲染。这个时候我们就用到了useMemo
:
import React, { useState } from 'react'
import { Input } from 'antd'
import Son1 from './son1'
interface Iprops {}
const Home: React.FC<Iprops> = () => {
const [info, setInfo] = useState('')
const [visible, setVisible] = useState(true)
const onVisible = useMemo(() => {
return () => {
setVisible((visible) => !visible)
}
}, [])
const changeInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setInfo(value)
}
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<p>{info}</p>
<Input onChange={(e) => changeInfo(e)}></Input>
<Son1 onVisible={onVisible} />
</div>
)
}
export default Home
可以看到,我们利用useMemo
将onVisible
缓存起来了,我们在useMemo
的第二个参数传入了一个[]
,表明它只会在渲染时执行一次,这里的用法跟useEffect
一样,[]
传入依赖项,当依赖项改变时,我们缓存的值才会重新计算。再次在输入框输入新的值,我们发现子组件不渲染了。
useMemo
一般用于计算比较复杂的场景
useCallback
如果掌握了useMemo
,那掌握 useCallback
简直不在话下。我们先来看看定义:
const memoizedCallback = useCallback(() => {
doSomething(a, b)
}, [a, b])
在 a
和b
的变量值不变的情况下,memoizedCallback
的引用不变。即:useCallback
的第一个入参函数会被缓存,从而达到渲染性能优化的目的。是不是跟useMemo
很像?useMemo
是缓存值,useCallback
一个是缓存函数的引用。也就是说 useCallback(fn, [deps])
相当于 useMemo(() => fn, [deps])
。我们现在用 useCallback
来改造一下刚刚上面 👆 那个例子:
....
const Home: React.FC<Iprops> = () => {
const [info, setInfo] = useState('')
const [visible, setVisible] = useState(true)
// const onVisible = useMemo(() => {
// return () => {
// setVisible((visible) => !visible)
// }
// }, [])
const onVisible = useCallback(() => {
setVisible(visible => !visible)
}, [])
const changeInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setInfo(value)
}
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<p>{info}</p>
<Input onChange={(e) => changeInfo(e)}></Input>
<Son1 onVisible={onVisible} />
</div>
)
}
export default Home
自定义 hook
借助于react
提供的基础hook
,我们通常也可以自定义hook
,react
规定我们自定义hook
时,必须以use
开头。我们来尝试自定义一个控制对话框的hook
:
import { useState } from 'react'
type returnd = [boolean, (visible?: boolean) => void]
const useVisible = (initVisible = false): returnd => {
const [visible, setVisible] = useState(initVisible)
function onVisible(value?: boolean) {
const newValue = value === undefined ? !visible : value
setVisible(newValue)
}
return [visible, onVisible]
}
export default useVisible
首先我们利用useState
声明了visible
和setVisible
,然后我们定义了onVisible
这个函数用来更改visible
,接着我们返回[visible, onVisible]
。然后我们来看看如何使用:
import { Button, Modal } from 'antd'
import useVisible from '../hooks/useVisible'
const Home: React.FC = () => {
const [visible, setVisible] = useVisible(false)
const modalShow = (value: boolean) => {
setVisible(value)
}
return (
<div style={{ marginTop: '5px', marginLeft: '400px', marginRight: '400px' }}>
<Button type='primary' onClick={() => modalShow(true)}>
Open Modal
</Button>
<Modal title='Basic Modal' visible={visible} onOk={() => modalShow(false)} onCancel={() => modalShow(false)}>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
)
}
export default Home
就像我们使用其他hook
一样方便。我们在写业务(搬砖)的过程中,我们可以尝试去将一些可复用的逻辑或者操作封装为我们自己的hook
,这才是hooks
的强大之处。
项目配置
我们在开发react
的时候,不免需要一些配置,例如别名、跨域等等。vue
给我们提供了一个vue.config.js
用于配置,那么react
项目呢?我们需要用到react-app-rewired
和customize-cra
:
yarn add react-app-rewired -D
yarn add customize-cra -D
安装完之后,我们需要更改一下我们的package.json
文件:
....
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
},
....
接着我们需要在项目的根目录新建一个config-overrides.js
文件。接下来我们对项目进行一些配置:
/* config-overrides.js */
const path = require('path')
const { override, addWebpackResolve, fixBabelImports, overrideDevServer } = require('customize-cra')
const { addReactRefresh } = require('customize-cra-react-refresh')
// 配置开发环境跨域
const devServerConfig = () => (config) => {
return {
...config,
port: 3000,
proxy: {
'/mock/158/airi': {
target: 'https://api.guaik.org',
changeOrigin: true,
secure: false,
},
},
}
}
module.exports = {
webpack: override(
// 热加载
addReactRefresh(),
// 配置路径别名
addWebpackResolve({
alias: {
'@': path.resolve(__dirname, 'src'),
},
}),
// antd 按需加载
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
})
),
devServer: overrideDevServer(devServerConfig()),
}