面试官:说说 React 高阶组件,并指出应用场景

高阶组件的概念

维基百科中表明高阶组件的定义至少要满足两个条件:1. 接受一个或多个函数作为输入2. 输出一个函数。

高阶组件和 JavaScript 语言中的高阶函数在本质上是一样的, 像 map filter 等都是高阶函数。 回归到高阶组件,其实就是在高阶组件中传入一个组件,然后返回一个经过处理的新组件。

React 官方认为,高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。简而言之,高阶组件是参数为组件,返回值为新组件的函数 。像我们常用的状态管理库 Redux 中的 connect 就是一个高阶组件,react-router 中的 widthRouter 也是一个高阶组件。

混淆点: 高阶组件其实是一个函数,不是一个组件;传入高阶组件的参数是一个组件,返回值也是一个组件。

我们不妨来定义一个高阶组件:

jsx 复制代码
import React, { PureComponent } from 'react'

// 定义一个高阶组件
function enhanceComponent(WrappedComponent) {
  // 定义一个类组件
  class AdvComponent extends PureComponent {
    render() {
      return <WrappedComponent/>
    }
  }
  return AdvComponent
}

class Home extends PureComponent {
  render() {
    return <h1>学习笔记</h1>
  }
}

const HomeHOC = enhanceComponent(Home)

export class App extends PureComponent {
  render() {
    return (
      <div>
        <HomeHOC/>
      </div>
    )
  }
}

export default App

高阶组件的应用场景

1. 代码复用、增强 Props

我们可以利用高阶组件复用公共逻辑,增强父组件给子组件的 props 传值。

比如多个父组件用 Context 方法给多个子组件传相同的值

jsx 复制代码
import React from 'react'
const UserContext = React.createContext({})
const {Provider, Consumer} = UserContext

// 定义老师类
class Teacher1 extends React.PureComponent{
  render() {
    return (
      <div>
        <Children1/>
      </div>
    )
  }
}

class Teacher2 extends React.PureComponent{
  render() {
    return (
      <div>
        <Children2/>
      </div>
    )
  }
}

// 定义学生类
class Children1 extends React.PureComponent{
  render() {
    return (
      <Consumer>{
        value =>{
          return (
            <div>
              <p>{value.name}</p>
              <p>{value.age}</p>
            </div>
          )
        }
      }</Consumer>
    )
  }
}

class Children2 extends React.PureComponent{
  render() {
    return (
      <Consumer>{
        value =>{
          return (
            <div>
              <p>{value.name}</p>
              <p>{value.age}</p>
            </div>
          )
        }
      }</Consumer>
    )
  }
}

class App extends React.PureComponent{
  constructor(props){
    super(props)
  }
  render(){
    return (
      <Provider value={{name:'编程老师', age:28}}>
        <Teacher1/>
        <Teacher2/>
      </Provider>
    )
  }
}

export default App

从上面代码中我们能够看到,在老师类和学生类中存在重复的代码,这样写显然是冗余的,那么我们就可以利用高阶组件复用公共逻辑并实现增强 props

jsx 复制代码
import React from 'react'
const UserContext = React.createContext({})
const {Provider, Consumer} = UserContext

// 定义学生类
class Children1 extends React.PureComponent{
  render() {
    return (
      <Consumer>{
        value =>{
          return (
            <div>
              <p>{this.props.name}</p>
              <p>{this.props.age}</p>
            </div>
          )
        }
      }</Consumer>
    )
  }
}

class Children2 extends React.PureComponent{
  render() {
    return (
      <Consumer>{
        value =>{
          return (
            <div>
              <p>{this.props.name}</p>
              <p>{this.props.age}</p>
            </div>
          )
        }
      }</Consumer>
    )
  }
}

// 定义高阶组件
function EnhancedComponent (WrappedComponent) {
  class Teacher extends React.PureComponent{
    render() {
      return (
        <Consumer>{
          value =>{
            return (
              <WrappedComponent name={value.name} age={value.age}/>
            )
          }
        }</Consumer>
      )
    }
  }
  return Teacher
}

// 使用高阶组件
const Teacher1 = EnhancedComponent(Children1)
const Teacher2 = EnhancedComponent(Children2)

class App extends React.PureComponent{
  constructor(props){
    super(props)
  }
  render(){
    return (
      <Provider value={{name:'编程老师', age:28}}>
        <Teacher1/>
        <Teacher2/>
      </Provider>
    )
  }
}

export default App

2. 抽离 State

我们可以利用高阶组件实现对 state 的抽离,一同 props 传给子组件。

举个例子,使用高阶组件之前

jsx 复制代码
import React from 'react'
import {EventEmitter} from 'events'

// 定义学生类
class Children1 extends React.PureComponent{
 constructor(props){
    super(props)
    this.state = {
        list: []
    }
  }
  componentDidMount() {
    eventBus.addListener('update', this.update)
  }
  componentWillUnmount() {
    eventBus.removeListener('update', this.update)
  }
  update = (list) => {
    this.setState({
      list: list
    })
  }
  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.age}</p>
        <p>{this.props.country}</p>
        {
          this.props.list.map(name=>{
            return <p key={name}>{name}</p>
          })
        }
      </div>
    )
  }
}

// 定义学生类
class Children2 extends React.PureComponent{
 constructor(props){
    super(props)
    this.state = {
        list: []
    }
  }
  componentDidMount() {
    eventBus.addListener('update', this.update)
  }
  componentWillUnmount() {
    eventBus.removeListener('update', this.update)
  }
  update = (list) => {
    this.setState({
      list: list
    })
  }
  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.age}</p>
        <p>{this.props.country}</p>
        {
          this.props.list.map(name=>{
            return <p key={name}>{name}</p>
          })
        }
      </div>
    )
  }
}

class App extends React.PureComponent{
  constructor(props){
    super(props)
  }
  render(){
    return (
      <Provider value={{name:'编程老师', age:28}}>
        <Teacher1 country={'中国'}/>
        <Teacher2 country={'中国'}/>
        <button onClick={()=>{this.btnClick()}}>点击</button>
      </Provider>
    )
  }
  btnClick(){
    eventBus.emit('update', ['JavaScript', 'React', 'Node']);
  }
}

export default App

我们从上面的代码中就可以看出,子组件对于 state 的处理有着相同的逻辑,又是定义事件总线,又是调用更新函数,我们是否可以利用高阶组件使其逻辑抽离变得更简洁?

jsx 复制代码
import React from 'react'
import {EventEmitter} from 'events'

// 定义学生类
class Children1 extends React.PureComponent{
  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.age}</p>
        <p>{this.props.country}</p>
        {
          this.props.list.map(name=>{
            return <p key={name}>{name}</p>
          })
        }
      </div>
    )
  }
}

// 定义学生类
class Children2 extends React.PureComponent{
  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.age}</p>
        <p>{this.props.country}</p>
        {
          this.props.list.map(name=>{
            return <p key={name}>{name}</p>
          })
        }
      </div>
    )
  }
}

// 定义高阶组件
function EnhancedComponent (WrappedComponent) {
  class Teacher extends React.PureComponent{
    constructor(props){
      super(props)
      this.state = {
          list: []
      }
    }
    componentDidMount() {
      eventBus.addListener('update', this.update)
    }
    componentWillUnmount() {
      eventBus.removeListener('update', this.update)
    }
    update = (list) => {
      this.setState({
        list: list
      })
    }
    render() {
      return (
        <Consumer>{
          value =>{
            return (
              <WrappedComponent {...value} {...this.props} {...this.state}/>
            )
          }
        }</Consumer>
      )
    }
  }
  return Teacher
}

// 使用高阶组件
const Teacher1 = EnhancedComponent(Children1)
const Teacher2 = EnhancedComponent(Children2)

class App extends React.PureComponent{
  constructor(props){
    super(props)
  }
  render(){
    return (
      <Provider value={{name:'编程老师', age:28}}>
        <Teacher1 country={'中国'}/>
        <Teacher2 country={'中国'}/>
        <button onClick={()=>{this.btnClick()}}>点击</button>
      </Provider>
    )
  }
  btnClick(){
    eventBus.emit('update', ['JavaScript', 'React', 'Node'])
  }
}

export default App

3. 权限控制

我们可以使用高阶组件实现用户登录的一个登录鉴权功能

jsx 复制代码
import React from 'react'

// 定义用户信息类
class Info extends React.PureComponent{
  render() {
    return (
      <div>用户信息</div>
    )
  }
}

定义用户登录类
class Login extends React.PureComponent{
  render() {
    return (
      <div>用户登录</div>
    )
  }
}

// 定义高阶函数 登录鉴权
function EnhancedComponent (WrappedComponent) {
  class Authority extends React.PureComponent{
    render() {
      if(this.props.isLogin){
        return <Info/>
      }else{
        return <Login/>
      }
    }
  }
  return Authority
}

// 实现高阶函数
const AuthorityInfo = EnhancedComponent(Info)

class App extends React.PureComponent{
  constructor(props){
    super(props)
  }
  render(){
    return (
      <AuthorityInfo isLogin={true}/>
    )
  }
}

export default App

4. 拦截生命周期

我们可以使用高阶组件拦截生命周期并进行一些逻辑的实现

jsx 复制代码
import React, { PureComponent } from 'react'

// 定义一个页面类 假如此页面有很多数据要渲染
class OriginDetail extends PureComponent {
  render() {
    return (
      <div>
        <h2>Detail Page</h2>
        <ul>
          <li>数据列表1</li>
          <li>...</li>
          <li>数据列表1000</li>
        </ul>
      </div>
    )
  }
}

// 定义一个高阶函数 能够拦截其生命周期函数 并计算当前页面渲染共用了多长时间
function logRenderTime(OriginComponent) {
  return class extends PureComponent {
    UNSAFE_componentWillMount() {
      this.beginTime = new Date().getTime()
    }
  
    componentDidMount() {
      this.endTime = new Date().getTime()
      const interval = this.endTime - this.beginTime
      console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`)
    }

    render() {
      return <OriginComponent {...this.props}/>
    }
  }
}

// 实现高阶函数
const Detail = logRenderTime(OriginDetail)

class App extends PureComponent {
  render() {
    return (
      <div>
        <Detail/>
      </div>
    )
  }
}

export default App

总结

我们利用高阶组件可以对代码进行逻辑的复用、增强 props、抽离 state、权限控制、拦截生命周期等操作,这确实在一定程度上能够优化我们的代码,对代码进行优雅的处理。

但是掘友们有没有发现,上述我们写的代码是不是存在着很多逻辑上的嵌套,包裹着很多代码,如果大量的使用高阶组件,对代码调试有一定的困难,对于一些刚入门的 React 来讲,也很难读懂代码。

Hooks 的出现是开创性的,自 React 16.8 版本出现之后,我们就可以使用 Hooks 来更高效率的编写我们的代码,解决了很多 React 之前存在的问题,React 新的官网,即 React 18 版本的官方文档现在已经大部分全部使用函数式组件举例,Hooks 出现之后就可以不用书写复杂的类式组件,而是建议更多的书写函数式组件,更多的使用 Hooks。

温馨提示: 我们在学习 React 的时候,如果阅读英文文档有些困难,可以去 React 中文网 查阅中文文档,对于一些想要通过类式组件来学习 React 的掘友们,也可以去旧版 React 官网查阅相关资料,也是有很多干货的。

相关推荐
@大迁世界4 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路13 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug16 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213818 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中40 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路43 分钟前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端