组件类
组件类,详细分的话有三种类,第一类说白了就是我平时用于继承的基类组件Component,PureComponent,还有就是react提供的内置的组件,比如Fragment,StrictMode,另一部分就是高阶组件forwardRef,memo等。
Component
Component
是 class
组件的根基。类组件一切始于 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(){ }, enqueueReplaceState(){}, enqueueForceUpdate(){ } } }
|
对于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> } }
|
点击按钮,没有任何反应,因为PureComponent会比较两次data对象,都指向同一个data,没有发生改变,所以不更新视图。
解决这个问题很简单,只需要在handerClick事件中这么写:
1
| this.setState({data: {...data}})
|
浅拷贝就能解决问题
memo
React.memo
和 PureComponent
作用类似,可以用作性能优化,React.memo
是高阶组件,函数组件和类组件都可以使用,和区别 PureComponent
是 React.memo
只能对
props的情况确定是否渲染,而 PureComponent
是针对 props
和 state
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 ){ return true }else if(pre.number !== next.number && next.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> } }
|
效果
react
不允许 ref
通过 props
传递,因为组件上已经有 ref
这个属性,在组件调和过程中,已经被特殊处理,forwardRef
出现就是解决这个问题,把 ref
转发到自定义的 forwardRef
定义的属性上,让ref
,可以通过props
传递。
lazy
React.lazy
和 suspense
配合一起用,能够有动态加载组件的效果。React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该
Promise
需要 resolve
一个d efault export
的 React
组件
模拟一个动态加载的场景
父组件
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> } }
|
效果
Suspense
何为Suspense
, Suspense
让组件“等待”某个异步操作,直到该异步操作结束即可渲染。
用于数据获取的 Suspense
是一个新特性,你可以使用 以声明的方式来“等待”任何内容,包括数据。本文重点介绍它在数据获取的用例,它也可以用于等待图像、脚本或其他异步的操作。
上面讲到高阶组件lazy
时候,已经用 lazy + Suspense
模式,构建了异步渲染组件。我们看一下官网文档中的案例:
1 2 3 4
| const ProfilePage = React.lazy(() => import('./ProfilePage')); <Suspense fallback={<Spinner />}> <ProfilePage /> </Suspense>
|