React组件抽象(二):高阶组件

本文深入探讨React中高阶组件(HOC)的概念,包括其作用、实现方式及应用场景,对比属性代理与反向继承两种方法,并讨论带参数HOC及其实现技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React组件抽象(一):高阶组件

前面介绍了组件抽象的方法:mixin。现在我们来介绍下另一种重要的高阶组件(Higher-order Component),简写HOC。

一、高阶组件是什么,为什么要使用?

组件被包裹,返回一个增强的组件。

打个比方,HOC像钢铁侠的机甲,对于普通人来说,穿上就是复联超人了,这个人并不一定需要关心内部怎么机甲实现方式。

为啥要用?被包裹组件不需要关心高阶组件发生了什么,逻辑基本上相互独立,没有啥感知。

二、如何实现高阶组件

高阶组件实现方式,大体有两种:

  • 属性代理(常用)
  • 反向继承

这两种方法基本都能做到类似或者同样的事情,比如渲染劫持控制props

渲染劫持:高阶组件根据内部自己的状态控制被包裹组件的显示状态,比如未加载配置需要Loading等;
控制props:你都被人包住了,props不都得随便人操作:

2.1 属性代理(推荐)

通过包裹组件,给组件传递处理好的props。

  • 简单的例子
import React from "react";

const HOCWrapper = WrappedComponent => {
  const userInfo = {name:"张全蛋"}
  return class extends React.Component{
    render() {
      return  <WrappedComponent userInfo={userInfo} {...this.props}/>;
    }
  }
}

class List extends React.Component{
  render(){
    return <div>{this.props.userInfo.name}</div>
  }
}

export default HOCWrapper(List)

如上述例子HOCWrapper内部可以做一些操作,获得用户信息。子组件被高阶组件包裹、属性经过代理,就多了userInfo的props属性。被包裹的组件可以直接使用props里面的userInfo信息。

使用修饰器之后可以写成这样:

@HOCWrapper
export default class Main extends React.Component{
  render(){
    return <div>{this.props.userInfo.name}</div>
  }
}

2.2 反向继承

根据文字知道,高阶组件继承了被包裹组件,返回了一个新的组件。

  • 渲染劫持
import React from "react";

const HOCWrapper = WrappedComponent => {
  return class extends WrappedComponent{
    state = {loading:true}
    componentDidMount(){
      fetchData().then(data => {
        this.setState({loading: false})
      })
    }
    render() {
      if(this.state.loading){
        return <Loading/>
      }
      return  (
        <div>
          {super.render()}
        </div>
      )
    }
  }
}

class Main extends React.Component{
  render(){
    return <div>MAIN</div>
  }
}

  • 控制props
import React from "react";

const HOCWrapper = WrappedComponent => {
  return class extends WrappedComponent{
    render() {
      const node = super.render();
      const props = Object.assign({},node.props,{userInfo:this.state.userInfo})
      const newNode = React.cloneElement(node,props,node.props.children);
      return  (
        <div>
          {newNode}
        </div>
      )
    }
  }
}
  • 控制state
    控制高阶组件的state就相当于控制被包裹组件的state。
import React from "react";

const HOCWrapper = WrappedComponent => {
  return class extends WrappedComponent{
    changeName = () =>{
      this.setState({name:"唐马儒"})
    }
    render() {
      return  (
        <div onClick={this.changeName}>
          {super.render()}
        </div>
      )
    }
  }
}

class Main extends React.Component{
  state = {name:"张全蛋"}
  render(){
    return <div>MAIN</div>
  }
}

虽然控制被包裹组件state很方便,但是这样会给组件带来隐藏的state,无法像props一样能够预测渲染结果。

mixin给组件注入新功能的时候,也可能导致加入一些隐藏的状态,维护组件的人难免会踩坑;

所以建议要尽量少用这种方式。

三、其他

3.1 带参数的高阶组件

相当于调用方法,方法返回值是一个高阶组件的wrapper。

需求是这样的:每个组件都要控制权限,权限在用户信息里面。组件的权限Code是确定的。


import React from "react";

const HOCWrapper = permissionCode => WrappedComponent => {
  const userInfo = {name:"张全蛋",permissionList:["100.200.300"]};
  return class extends React.Component{
    render() {
      if(userInfo.permissionList.includes(permissionCode)){
        return <div>没有权限! Permit denied!</div>
      }
      return  <WrappedComponent userInfo={userInfo} {...this.props}/>;
    }
  }
}

@HOCWrapper("100.200.300")
export default class Main extends React.Component{
  render(){
    return <div>{this.props.userInfo.name}</div>
  }
}

这里执行了HOCWrapper("100.200.300")是不是返回了一个高阶组件工具呢?

3.2 带来的问题

习惯用React-dev-tool的同学们肯定知道在使用Redux的时候会有一堆的connent();层次嵌套,一个页面最高有几十个组件嵌套,看起来非常头疼。(没错,redux就是高阶组件的使用方式)
这时候需要这样处理:

  • 给组件命名
import React from "react";

const HOCWrapper = WrappedComponent => {
  const userInfo = {name:"张全蛋"}
  const HOC = class extends React.Component{
    render() {
      return  <WrappedComponent userInfo={userInfo} {...this.props}/>;
    }
  }
  HOC.displayName = `HOC-${WrappedComponent.displayName}`
  return HOC;
}

  • React 16.8 Hook
    用hook 替换redux,有兴趣的可以了解下

3.3 贴近项目的使用场景

单页应用在每次刷新页面的时候去服务器获取用户信息,获取数据期间需要显示Loading。返回成功后才显示页面。



import React from "react";

const HOCWrapper = permissionCode => WrappedComponent => {
  return class extends React.Component{
    state = {
      loading:true,
      userInfo:{
        name:"张全蛋",
        permissionList:[]
      }
    };
    // 组件加载完成获取用户数据
    componentDidMount() {
      fetchData("getUserInfo").then(data =>{
        this.setState({
          loading:false,
          userInfo:data
        })
      })
    }

    render() {
      // 刚开始未获取到数据的时候显示Loading
      if(this.state.loading){
        return <Loading/>
      }
      const { userInfo } = this.props;
      // 获取到数据后校验权限
      if(userInfo.permissionList.includes(permissionCode)){
        return <div>没有权限! Permit denied!</div>
      }
      // 正常渲染数据
      return  <WrappedComponent userInfo={userInfo} {...this.props}/>;
    }
  }
}

@HOCWrapper("100.200.300")
export default class Main extends React.Component{
  render(){
    return <div>{this.props.userInfo.name}</div>
  }
}

3.4 建议或者总结

属性代理的方式是最常见的,简单好理解。

反向继承我觉得有秀操作的嫌疑,但是我没有证据。

高阶组件+修饰器是绝配,在你的项目里用起来吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值