0%

React进阶

组件类

组件类,详细分的话有三种类,第一类说白了就是我平时用于继承的基类组件Component,PureComponent,还有就是react提供的内置的组件,比如Fragment,StrictMode,另一部分就是高阶组件forwardRef,memo等。

House in Provence

Component

Componentclass 组件的根基。类组件一切始于 Component

react/src/ReactBaseClasses.js

1
2
3
4
5
6
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}

我们声明的类组件是什么时候以何种形式被实例化的呢?

react-reconciler/src/ReactFiberClassComponent.js

constructClassInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function constructClassInstance(
workInProgress,
ctor,
props
){
const instance = new ctor(props, context);
instance.updater = {
isMounted,
enqueueSetState(){
/* setState 触发这里面的逻辑 */
},
enqueueReplaceState(){},
enqueueForceUpdate(){
/* forceUpdate 触发这里的逻辑 */
}
}
}

对于component,react处理逻辑还是很简单的,实例化我们类组件,然后赋值updater对象,负责组件的更新。然后在组件各个阶段,执行类组件的render函数和对应的声明周期

PureComponent

PureComponent和Component用法,差不多一样,唯一不同的是,纯组件PureComponent会浅比较,props和state是否相同,来决定是否重新渲染组件。所以一般用于性能调优,减少render次数

什么是浅比较

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
class Index extends React.PureComponent{
constructor(props){
super(props)
this.state={
data:{
name:'alien',
age:28
}
}
}
handerClick= () =>{
const { data } = this.state
data.age++
this.setState({ data })
}
render(){
const { data } = this.state
return <div className="box" >
<div className="show" >
<div> 你的姓名是: { data.name } </div>
<div> 年龄: { data.age }</div>
<button onClick={ this.handerClick } >age++</button>
</div>
</div>
}
}

img

点击按钮,没有任何反应,因为PureComponent会比较两次data对象,都指向同一个data,没有发生改变,所以不更新视图。

解决这个问题很简单,只需要在handerClick事件中这么写:

1
this.setState({data: {...data}})

浅拷贝就能解决问题

memo

React.memoPureComponent 作用类似,可以用作性能优化,React.memo 是高阶组件,函数组件和类组件都可以使用,和区别 PureComponentReact.memo只能对
props的情况确定是否渲染,而 PureComponent 是针对 propsstate

React.memo 接受两个参数,第一个参数原始组件本身,第二个参数,可以根据一次更新中props是否相同决定原始组件是否重新。是一个返回布尔值,true 证明组件无须重新渲染, false 证明
组件需要重新渲染,这个和类组件中的 shouldComponentUpdate() 正好相反

React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。

shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染。

接下来我们做一个场景,控制组件在仅此一个props数字变量,一定范围渲染。

控制 props 中的number:

  • 1只有 number 更改,组件渲染
  • 2只有 number 小于5,组件渲染
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
34
35
36
37
38
39
40
41
42
43
function TextMemo(props) {
console.log('子组件渲染')
if (props){
return <div>hello,world</div>
}
}

const controlIsRender = (pre,next) => {
if(pre.number === next.number ){ // number 不改变 ,不渲染组件
return true
}else if(pre.number !== next.number && next.number > 5 ) { // number 改变 ,但值大于5 , 不渲染组件
return true
}else { // 否则渲染组件
return false
}
}

const NewTexMemo = memo(TextMemo, controlIsRender)
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:1,
num:1
}
}
render(){
const { num , number } = this.state
return <div>
<div>
改变num:当前值 { num }
<button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button>
<button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button>
</div>
<div>
改变number: 当前值 { number }
<button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button>
<button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button>
</div>
<NewTexMemo num={ num } number={number} />
</div>
}
}

React.memo一定程度上,可以等价于组件外部使用 shouldComponentUpdate,用于拦截新老props,确定组件是否更新

forwardRef

forwardRef 应用场景

1)转发引入Ref

这个场景实际很简单,比如父组件想要获取孙组件,某一个 dom 元素,这种隔代 ref 获取引用,就需要 forwardRef 来助力。

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
34
35
36
function Son (props) {
const { grandRef } = props
return (<div>
<div>i am alien</div>
</div>)
}

class Father extends React.Component {
constructor (props) {
super(props)
}
render () {
return (
<div>
<Son grandRef={this.props.grandRef} />
</div>
)
}
}

const NewFather = React.forwardRef((props,ref)=><Father grandRef={ref} {...props} /> )

class GrandFather extends React.Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div>
<NewFather ref={(node)=> this.node = node } />
</div>
}
}

效果

img

react 不允许 ref 通过 props 传递,因为组件上已经有 ref 这个属性,在组件调和过程中,已经被特殊处理,forwardRef 出现就是解决这个问题,把 ref 转发到自定义的 forwardRef 定义的属性上,让ref,可以通过props传递。

lazy

React.lazysuspense 配合一起用,能够有动态加载组件的效果。React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该
Promise 需要 resolve 一个d efault exportReact 组件

模拟一个动态加载的场景

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Test from './comTest'
const LazyComponent = React.lazy(()=> new Promise((resolve)=>{
setTimeout(()=>{
resolve({
default: ()=> <Test />
})
},2000)
}))
class index extends React.Component{
render(){
return <div className="context_box" style={ { marginTop :'50px' } } >
<React.Suspense fallback={ <div className="icon" ><SyncOutlined spin /></div> } >
<LazyComponent />
</React.Suspense>
</div>
}
}

我们用 setTimeout 来模拟 import 异步引入效果

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
console.log('--componentDidMount--')
}
render(){
return <div>
<img src={alien} className="alien" />
</div>
}
}

效果

213

Suspense

何为Suspense, Suspense 让组件“等待”某个异步操作,直到该异步操作结束即可渲染。

用于数据获取的 Suspense 是一个新特性,你可以使用 以声明的方式来“等待”任何内容,包括数据。本文重点介绍它在数据获取的用例,它也可以用于等待图像、脚本或其他异步的操作。

上面讲到高阶组件lazy时候,已经用 lazy + Suspense模式,构建了异步渲染组件。我们看一下官网文档中的案例:

1
2
3
4
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懒加载
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>