一 前言
React
高阶组件(HOC
),对于很多react
开发者来说并不陌生,它是灵活使用react
组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也是一个组件的函数。高阶作用用于强化组件,复用逻辑,提升渲染性能等作用。高阶组件也并不是很难理解,其实接触过后还是蛮简单的,接下来我将按照,高阶组件理解?,高阶组件具体怎么使用?应用场景, 高阶组件实践(源码级别) 为突破口,带大家详细了解一下高阶组件。本文篇幅比较长,建议收藏观看
我们带着问题去开始今天的讨论:
- 1 什么是高阶组件,它解决了什么问题?
- 2 有几种高阶组件,它们优缺点是什么?
- 3 如何写一个优秀高阶组件?
- 4
hoc
怎么处理静态属性,跨层级ref
等问题? - 5 高阶组件怎么控制渲染,隔离渲染?
- 6 高阶组件怎么监控原始组件的状态?
- ...
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
二 全方位看高阶组件
1 几种包装强化组件的方式
① mixin模式
原型图
老版本的react-mixins
在react
初期提供一种组合方法。通过React.createClass
,加入mixins
属性,具体用法和vue
中mixins
相似。具体实现如下。
const customMixin = {
componentDidMount(){
console.log( '------componentDidMount------' )
},
say(){
console.log(this.state.name)
}
}
const APP = React.createClass({
mixins: [ customMixin ],
getInitialState(){
return {
name:'alien'
}
},
render(){
const {
name } = this.state
return <div> hello ,world , my name is {
name } </div>
}
})
这种mixins
只能存在createClass
中,后来React.createClass
连同mixins
这种模式被废弃了。mixins
会带来一些负面的影响。
- 1 mixin引入了隐式依赖关系。
- 2 不同mixins之间可能会有先后顺序甚至代码冲突覆盖的问题
- 3 mixin代码会导致滚雪球式的复杂性
衍生方式
createClass
的废弃,不代表mixin
模式退出react
舞台,在有状态组件class
,我们可以通过原型链继承来实现mixins
。
const customMixin = {
/* 自定义 mixins */
componentDidMount(){
console.log( '------componentDidMount------' )
},
say(){
console.log(this.state.name)
}
}
function componentClassMixins(Component,mixin){
/* 继承 */
for(let key in mixin){
Component.prototype[key] = mixin[key]
}
}
class Index extends React.Component{
constructor(){
super()
this.state={
name:'alien' }
}
render(){
return <div> hello,world
<button onClick={
this.say.bind(this) } > to say </button>
</div>
}
}
componentClassMixins(Index,customMixin)
②extends继承模式
原型图
在class
组件盛行之后,我们可以通过继承的方式进一步的强化我们的组件。这种模式的好处在于,可以封装基础功能组件,然后根据需要去extends
我们的基础组件,按需强化组件,但是值得注意的是,必须要对基础组件有足够的掌握,否则会造成一些列意想不到的情况发生。
class Base extends React.Component{
constructor(){
super()
this.state={
name:'alien'
}
}
say(){
console.log('base components')
}
render(){
return <div> hello,world <button onClick={
this.say.bind(this) } >点击</button> </div>
}
}
class Index extends Base{
componentDidMount(){
console.log( this.state.name )
}
say(){
/* 会覆盖基类中的 say */
console.log('extends components')
}
}
export default Index
③HOC模式
原型图
HOC
是我们本章主要的讲的内容,具体用法,我们接下来会慢慢道来,我们先简单尝试一个HOC
。
function HOC(Component) {
return class wrapComponent extends React.Component{
constructor(){
super()
this.state={
name:'alien'
}
}
render=()=><Component {
...this.props } {
...this.state } />
}
}
@HOC
class Index extends React.Component{
say(){
const {
name } = this.props
console.log(name)
}
render(){
return <div> hello,world <button onClick={
this.say.bind(this) } >点击</button> </div>
}
}
④自定义hooks模式
原型图
hooks
的诞生,一大部分原因是解决无状态组件没有state
和逻辑难以复用问题。hooks
可以将一段逻辑封装起来,做到开箱即用,我这里就不多讲了,接下来会出react-hooks
原理的文章,完成react-hooks
三部曲。感兴趣的同学可以看笔者的另外二篇文章,里面详细介绍了react-hooks
复用代码逻辑的原则和方案。
传送门:
玩转react-hooks,自定义hooks设计模式及其实战
2 高阶组件产生初衷
组件是把prop
渲染成UI
,而高阶组件是将组件转换成另外一个组件,我们更应该注意的是,经过包装后的组件,获得了那些强化,节省多少逻辑,或是解决了原有组件的那些缺陷,这就是高阶组件的意义。我们先来思考一下高阶组件究竟解决了什么问题🤔🤔🤔?
① 复用逻辑:高阶组件更像是一个加工react
组件的工厂,批量对原有组件进行加工,包装处理。我们可以根据业务需求定制化专属的HOC
,这样可以解决复用逻辑。
② 强化props:这个是HOC
最常用的用法之一,高阶组件返回的组件,可以劫持上一层传过来的props
,然后混入新的props
,来增强组件的功能。代表作react-router
中的withRouter
。
③ 赋能组件:HOC
有一项独特的特性,就是可以给被HOC
包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种HOC
,可能需要和业务组件紧密结合。典型案例react-keepalive-router
中的 keepaliveLifeCycle
就是通过HOC
方式,给业务组件增加了额外的生命周期。
④ 控制渲染:劫持渲染是hoc
一个特性,在wrapComponent
包装组件中,可以对原来的组件,进行条件渲染
,节流渲染
,懒加载
等功能,后面会详细讲解,典型代表做react-redux
中connect
和 dva
中 dynamic
组件懒加载。
我会针对高阶组件的初衷展开,详细介绍其原理已经用法。跟上我的思路,我们先来看一下,高阶组件如何在我们的业务组件中使用的。
3 高阶组件使用和编写结构
HOC
使用指南是非常简单的,只需要将我们的组件进行包裹就可以了。
使用:装饰器模式和函数包裹模式
对于class
声明的有状态组件,我们可以用装饰器模式,对类组件进行包装:
@withStyles(styles)
@withRouter
@keepaliveLifeCycle
class Index extends React.Componen{
/* ... */
}
我们要注意一下包装顺序,越靠近Index
组件的,就是越内层的HOC
,离组件Index
也就越近。
对于无状态组件(函数声明)我们可以这么写:
function Index(){
/* .... */
}
export default withStyles(styles)(withRouter( keepaliveLifeCycle(Index) ))
模型:嵌套HOC
对于不需要传递参数的HOC
,我们编写模型我们只需要嵌套一层就可以,比如withRouter
,
function withRouter(){
return class wrapComponent extends React.Component{
/* 编写逻辑 */
}
}
对于需要参数的HOC
,我们需要一层代理,如下:
function connect (mapStateToProps){
/* 接受第一个参数 */
return function connectAdvance(wrapCompoent){
/* 接受组件 */
return class WrapComponent extends React.Component{
}
}
}
我们看出两种hoc
模型很简单,对于代理函数,可能有一层,可能有很多层,不过不要怕,无论多少层本质上都是一样的,我们只需要一层一层剥离开,分析结构,整个hoc
结构和脉络就会清晰可见。吃透hoc
也就易如反掌。
4 两种不同的高阶组件
常用的高阶组件有两种方式正向的属性代理和反向的组件继承,两者之前有一些共性和区别。接下具体介绍两者区别,在第三部分会详细介绍具体实现。
正向属性代理
所谓正向属性代理,就是用组件包裹一层代理组件,在代理组件上,我们可以做一些,对源组件的代理操作。在fiber tree
上,先mounted
代理组件,然后才是我们的业务组件。我们可以理解为父子组件关系,父组件对子组件进行一系列强化操作。
function HOC(WrapComponent){
return class Advance extends React.Component{
state={
name:'alien'
}
render(){
return <WrapComponent {
...this.props } {
...this.state } />
}
}
}
优点
- ① 正常属性代理可以和业务组件低耦合,零耦合,对于
条件渲染
和props属性增强
,只负责控制子组件渲染和传递额外的props
就可以,所以无须知道,业务组件做了些什么。所以正向属性代理,更适合做一些开源项目的hoc
,目前开源的HOC
基本都是通过这个模式实现的。 - ② 同样适用于
class
声明组件,和function
声明的组件。 - ③ 可以完全隔离业务组件的渲染,相比反向继承,属性代理这种模式。可以完全控制业务组件渲染与否,可以避免
反向继承
带来一些副作用,比如生命周期的执行。 - ④ 可以嵌套使用,多个
hoc
是可以嵌套使用的,而且一般不会限制包装HOC
的先后顺序。
缺点
① 一般无法直接获取业务组件的状态,如果想要获取,需要
ref
获取组件实例。② 无法直接继承静态属性。如果需要继承需要手动处理,或者引入第三方库。
例子:
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
Index.say = function(){
console.log('my name is alien')
}
function HOC(Component) {
return class wrapComponent extends React.Component{
render(){
return <Component {
...this.props } {
...this.state } />
}
}
}
const newIndex = HOC(Index)
console.log(newIndex.say)
打印结果
反向继承
反向继承和属性代理有一定的区别,在于包装后的组件继承了业务组件本身,所以我们我无须在去实例化我们的业务组件。当前高阶组件就是继承后,加强型的业务组件。这种方式类似于组件的强化,所以你必要要知道当前
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
function HOC(Component){
return class wrapComponent extends Component{
/* 直接继承需要包装的组件 */
}
}
export default HOC(Index)
优点
- ① 方便获取组件内部状态,比如
state
,props
,生命周期,绑定的事件函数等 - ②
es6
继承可以良好继承静态属性。我们无须对静态属性和方法进行额外的处理。
class Index extends React.Component{
render(){
return <div> hello,world </div>
}
}
Index.say = function(){
console.log('my name is alien')
}
function HOC(Component) {
return class wrapComponent extends Component{
}
}
const newIndex = HOC(Index)
console.log(newIndex.say)
打印结果
缺点
- ① 无状态组件无法使用。
- ② 和被包装的组件强耦合,需要知道被包装的组件的内部状态,具体是做什么?
- ③ 如果多个反向继承
hoc
嵌套在一起,当前状态会覆盖上一个状态。这样带来的隐患是非常大的,比如说有多个componentDidMount
,当前componentDidMount
会覆盖上一个componentDidMount
。这样副作用串联起来,影响很大。
三 如何编写高阶组件
接下来我们来看看,如何编写一个高阶组件,你可以参考如下的情景,去编写属于自己的HOC
。
1 强化props
① 混入props
这个是高阶组件最常用的功能,承接上层的props
,在混入自己的props
,来强化组件。
有状态组件(属性代理)
function classHOC(WrapComponent){
return class Idex extends React.Component{
state={
name:'alien'
}
componentDidMount(){
console.log('HOC')
}
render(){
return <WrapComponent {
...this.props } {
...this.state } />
}
}
}
function Index(props){
const {
name } = props
useEffect(()=>{
console.log( 'index' )
},[])
return <div>
hello,world , my name is {
name }
</div>
}
export default classHOC(Index)
有状态组件(属性代理)
同样也适用与无状态组件。
function functionHoc(WrapComponent){
return function Index(props){
const [ state , setState ] = useState({
name :'alien' })
return <WrapComponent {
...props } {
...state } />
}
}
效果
② 抽离state控制更新
高阶组件可以将HOC
的state
的配合起来,控制业务组件的更新。这种用法在react-redux
中connect
高阶组件中用到过,用于处理来自redux
中state
更改,带来的订阅更新作用。
我们将上述代码进行改造。
function classHOC(WrapComponent){
return class Idex extends React.Component{
constructor(){
super()
this.state={
name:'alien'
}
}
changeName(name){
this.setState({
name })
}
render(){
return <WrapComponent {
...this.props } {
...this.state } changeName={
this.changeName.bind(this) } />
}
}
}
function Index(props){
const [ value ,setValue ] = useState(null)
const {
name ,changeName } = props
return <div>
<div> hello,world , my name is {
name }</div>
改变name <input onChange={
(e)=> setValue(e.target.value) } />
<button onClick={
()=> changeName(value) } >确定</button>
</div>
}
export default classHOC(Index)
效果
2 控制渲染
控制渲染是高阶组件的一个很重要的特性,上边说到的两种高阶组件,都能完成对组件渲染的控制。具体实现还是有区别的,我们一起来探索一下。
2.1 条件渲染
① 基础 :动态渲染
对于属性代理的高阶组件,虽然不能在内部操控渲染状态,但是可以在外层控制当前组件是否渲染,这种情况应用于,权限隔离,懒加载 ,延时加载等场景。
实现一个动态挂载组件的HOC
function renderHOC(WrapComponent){
return class Index extends React.Component{
constructor(props){
super(props)
this.state={
visible:true }
}
setVisible(){
this.setState({
visible:!this.state.visible })
}
render(){
const {
visible } = this.state
return <div className="box" >
<button onClick={
this.setVisible.bind(this) } > 挂载组件 </button>
{
visible ? <WrapComponent {
...this.props } setVisible={
this.setVisible.bind(this) } /> : <div className="icon" ><SyncOutlined spin className="theicon" /></div> }
</div>
}
}
}
class Index extends React.Component{
render(){
const {
setVisible } = this.props
return <div className="box" >
<p>hello,my name is alien</p>
<img src='https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=294206908,2427609994&fm=26&gp=0.jpg' />
<button onClick={
() => setVisible()} > 卸载当前组件 </button>
</div>
}
}
export default renderHOC(Index)
效果:
② 进阶 :分片渲染
是不是感觉不是很过瘾,为了让大家加强对HOC
条件渲染的理解,我再做一个分片渲染+懒加载功能。为了让大家明白,我也是绞尽脑汁啊😂😂😂。
进阶:实现一个懒加载功能的HOC,可以实现组件的分片渲染,用于分片渲染页面,不至于一次渲染大量组件造成白屏效果
const renderQueue = []
let isFirstrender = false
const tryRender = ()=>{
const render = renderQueue.shift()
if(!render) return
setTimeout(()=>{
render() /* 执行下一段渲染 */
},300)
}
/* HOC */
function renderHOC(WrapComponent){
return function Index(props){
const [ isRender , setRender ] = useState(false)
useEffect(()=>{
renderQueue.push(()=>{
/* 放入待渲染队列中 */
setRender(true)
})
if(!isFirstrender) {
tryRender() /**/
isFirstrender = true
}
},[])
return isRender ? <WrapComponent tryRender={
tryRender} {
...props } /> : <div className='box' ><div className="icon" ><SyncOutlined spin /></div></div>
}
}
/* 业务组件 */
class Index extends React.Component{
componentDidMount(){
const {
name , tryRender} = this.props
/* 上一部分渲染完毕,进行下一部分渲染 */
tryRender()
console.log( name+'渲染')
}
render(){
return <div>
<img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=294206908,2427609994&fm=26&gp=0.jpg" />
</div>
}
}
/* 高阶组件包裹 */
const Item = renderHOC(Index)
export default () => {
return <React.Fragment>
<Item name="组件一" />
<Item name="组件二" />
<Item name="组件三" />
</React.Fragment>
}
效果
大致流程,初始化的时候,HOC
中将渲染真正组件的渲染函数,放入renderQueue
队列中,然后初始化渲染一次,接下来,每一个项目组件,完成 didMounted
状态后,会从队列中取出下一个渲染函数,渲染下一个组件, 一直到所有的渲染任务全部执行完毕,渲染队列清空,有效的进行分片的渲染,这种方式对海量数据展示,很奏效。
用HOC
实现了条件渲染-分片渲染的功能,实际条件渲染理解起来很容易,就是通过变量,控制是否挂载组件,从而满足项目本身需求,条件渲染可以演变成很多模式,我这里介绍了条件渲染的二种方式,希望大家能够理解精髓所在。
③ 进阶:异步组件(懒加载)
不知道大家有没有用过dva
,里面的dynamic
就是应用HOC
模式实现的组件异步加载,我这里简化了一下,提炼核心代码,如下:
/* 路由懒加载HOC */
export default function AsyncRouter(loadRouter) {
return class Content extends React.Component {
state = {
Component: null}
componentDidMount() {
if (this.state.Component) return
loadRouter()
.then(module => module.default)
.then(Component => this.setState({
Component},
))
}
render() {
const {
Component} = this.state
return Component ? <Component {
...this.props
}
/> : null
}
}
}
使用
const Index = AsyncRouter(()=>import('../pages/index'))
hoc
还可以配合其他API
,做一下衍生的功能。如上配合import
实现异步加载功能。HOC
用起来非常灵活,
④ 反向继承 : 渲染劫持
HOC反向继承模式,可以实现颗粒化的渲染劫持,也就是可以控制基类组件的render
函数,还可以篡改props,或者是children
,我们接下来看看,这种状态下,怎么使用高阶组件。
const HOC = (WrapComponent) =>
class Index extends WrapComponent {
render() {
if (this.props.visible) {
return super.render()
} else {
return <div>暂无数据</div>
}
}
}
⑤ 反向继承:修改渲染树
修改渲染状态(劫持render替换子节点)
class Index extends React.Component{
render(){
return <div>
<ul>
<li>react</li>
<li>vue</li>
<li>Angular</li>
</ul>
</div>
}
}
function HOC (Component){
return class Advance extends Component {
render() {
const element = super.render()
const otherProps = {
name:'alien'
}
/* 替换 Angular 元素节点 */
const appendElement = React.createElement('li' ,{
} , `hello ,world , my name is ${
otherProps.name }` )
const newchild = React.Children.map(element.props.children.props.children,(child,index)=>{
if(index === 2) return appendElement
return child
})
return React.cloneElement(element, element.props, newchild)
}
}
}
export default HOC(Index)
效果
我们用劫持渲染的方式,来操纵super.render()
后的React.element
元素,然后配合 createElement
, cloneElement
, React.Children
等 api
,可以灵活操纵,真正的渲染react.element
,可以说是偷天换日,不亦乐乎。
2.2节流渲染
hoc
除了可以进行条件渲染,渲染劫持功能外,还可以进行节流渲染,也就是可以优化性能,具体怎么做,请跟上我的节奏往下看。
① 基础: 节流原理
hoc
可以配合hooks
的useMemo
等API
配合使用,可以实现对业务组件的渲染控制,减少渲染次数,从而达到优化性能的效果。如下案例,我们期望当且仅当num
改变的时候,渲染组件,但是不影响接收的props
。我们应该这样写我们的HOC
。
function HOC (Component){
return function renderWrapComponent(props){
const {
num } = props
const RenderElement = useMemo(() => <Component {
...props} /> ,[ num ])
return RenderElement
}
}
class Index extends React.Component{
render(){
console.log(`当前组件是否渲染`,this.props)
return <div>hello,world, my name is alien </div>
}
}
const IndexHoc = HOC(Index)
export default ()=> {
const [ num ,setNumber ] = useState(0)
const [ num1 ,setNumber1 ] = useState(0)
const [ num2 ,setNumber2 ] = useState(0)
return <div>
<IndexHoc num={
num } num1={
num1} num2={
num2 } />
<button onClick={
() => setNumber(num + 1) } >num++</button>
<button onClick={
() => setNumber1(num1 + 1) } >num1++</button>
<button onClick={
() => setNumber2(num2 + 1) } >num2++</button>
</div>
}
效果:
如图所示,当我们只有点击 num++
时候,才重新渲染子组件,点击其他按钮,只是负责传递了props
,达到了期望的效果。
② 进阶:定制化渲染流
思考:🤔上述的案例只是介绍了原理,在实际项目中,是量化生产不了的,原因是,我们需要针对不同props
变化,写不同的HOC
组件,这样根本起不了Hoc
真正的用途,也就是HOC
产生的初衷。所以我们需要对上述hoc
进行改造升级,是组件可以根据定制化方向,去渲染组件。也就是Hoc
生成的时候,已经按照某种契约去执行渲染。
function HOC (rule){
return function (Component){
return function renderWrapComponent(props){
const dep = rule(props)
const RenderElement = useMemo(() => <Component {
...props} /> ,[ dep ])
return RenderElement
}
}
}
/* 只有 props 中 num 变化 ,渲染组件 */
@HOC( (props)=> props['num'])
class IndexHoc extends React.Component{
render(){
console.log(`组件一渲染`,this.props)
return <div> 组件一 : hello,world </div>
}
}
/* 只有 props 中 num1 变化 ,渲染组件 */
@HOC((props)=> props['num1'])
class IndexHoc1 extends React.Component{
render(){
console.log(`组件二渲染`,this.props)
return <div> 组件二 : my name is alien </div>
}
}
export default ()=> {
const [ num ,setNumber ] = useState(0)
const [ num1 ,setNumber1 ] = useState(0)
const [ num2 ,setNumber2 ] = useState(0)
return <div>
<IndexHoc num={
num } num1={
num1} num2={
num2 } />
<IndexHoc1 num={
num } num1={
num1} num2={
num2 } />
<button onClick={
() => setNumber(num + 1) } >num++</button>
<button onClick={
() => setNumber1(num1 + 1) } >num1++</button>
<button onClick={
() => setNumber2(num2 + 1) } >num2++</button>
</div>
}
效果
完美实现了效果。这用高阶组件模式,可以灵活控制React
组件层面上的,props
数据流和更新流,优秀的高阶组件有 mobx
中observer
,inject
, react-redux
中的connect
,感兴趣的同学,可以抽时间研究一下。
3 赋能组件
高阶组件除了上述两种功能之外,还可以赋能组件,比如加一些额外生命周期
,劫持事件,监控日志等等。
3.1 劫持原型链-劫持生命周期,事件函数
① 属性代理实现
function HOC (Component){
const proDidMount = Component.prototype.componentDidMount
Component.prototype.componentDidMount = function(){
console.log('劫持生命周期:componentDidMount')
proDidMount.call(this)
}
return class wrapComponent extends React.Component{
render(){
return <Component {
...this.props} />
}
}
}
@HOC
class Index extends React.Component{
componentDidMount(){
console.log('———didMounted———')
}
render(){
return <div>hello,world</div>
}
}
效果
② 反向继承实现
反向继承,因为在继承原有组件的基础上,可以对原有组件的生命周期或事件进行劫持,甚至是替换。
function HOC (Component){
const didMount = Component.prototype.componentDidMount
return class wrapComponent extends Component{
componentDidMount(){
console.log('------劫持生命周期------')
if (didMount) {
didMount.apply(this) /* 注意 `this` 指向问题。 */
}
}
render(){
return super.render()
}
}
}
@HOC
class Index extends React.Component{
componentDidMount(){
console.log('———didMounted———')
}
render(){
return <div>hello,world</div>
}
}
3.2 事件监控
HOC
还可以对原有组件进行监控。比如对一些事件监控
,错误监控
,事件监听
等一系列操作。
① 组件内的事件监听
接下来,我们做一个HOC
,只对组件内的点击事件做一个监听效果。
function ClickHoc (Component){
return function Wrap(props){
const dom = useRef(null)
useEffect(()=>{
const handerClick = () => console.log('发生点击事件')
dom.current.addEventListener('click',handerClick)
return () => dom.current.removeEventListener('click',handerClick)
},[])
return <div ref={
dom} ><Component {
...props} /></div>
}
}
@ClickHoc
class Index extends React.Component{
render(){
return <div className='index' >
<p>hello,world</p>
<button>组件内部点击</button>
</div>
}
}
export default ()=>{
return <div className='box' >
<Index />
<button>组件外部点击</button>
</div>
}
效果
3 ref助力操控组件实例
对于属性代理我们虽然不能直接获取组件内的状态,但是我们可以通过ref
获取组件实例,获取到组件实例,就可以获取组件的一些状态,或是手动触发一些事件,进一步强化组件,但是注意的是:class
声明的有状态组件才有实例,function
声明的无状态组件不存在实例。
① 属性代理-添加额外生命周期
我们可以针对某一种情况, 给组件增加额外的生命周期,我做了一个简单的demo
,监听number
改变,如果number
改变,就自动触发组件的监听函数handerNumberChange
。
具体写法如下
function Hoc(Component){
return class WrapComponent extends React.Component{
constructor(){
super()
this.node = null
}
UNSAFE_componentWillReceiveProps(nextprops){
if(nextprops.number !== this.props.number ){
this.node.handerNumberChange && this.node.handerNumberChange.call(this.node)
}
}
render(){
return <Component {
...this.props} ref={
(node) => this.node = node } />
}
}
}
@Hoc
class Index extends React.Component{
handerNumberChange(){
/* 监听 number 改变 */
}
render(){
return <div>hello,world</div>
}
}
这种写法有点不尽人意,大家不要着急,在第四部分,源码实战中,我会介绍一种更好的场景。方便大家理解Hoc
对原有组件的赋能。
4 总结
上面我分别按照hoc
主要功能,强化props , 控制渲染 ,赋能组件 三个方向对HOC
编写做了一个详细介绍,和应用场景的介绍,目的让大家在理解高阶组件的时候,更明白什么时候会用到?,怎么样去写?`
里面涵盖的知识点我总一个总结。
对于属性代理HOC,我们可以:
- 强化props & 抽离state。
- 条件渲染,控制渲染,分片渲染,懒加载。
- 劫持事件和生命周期
- ref控制组件实例
- 添加事件监听器,日志
对于反向代理的HOC,我们可以:
- 劫持渲染,操纵渲染树
- 控制/替换生命周期,直接获取组件状态,绑定事件。
每个应用场景,我都举了例子🌰🌰,大家可以结合例子深入了解一下其原理和用途。