面试官:说说 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 官网查阅相关资料,也是有很多干货的。

相关推荐
果子切克now26 分钟前
vue2与vue3知识点
前端·javascript·vue.js
积水成江1 小时前
Vite+Vue3+SpringBoot项目如何打包部署
java·前端·vue.js·windows·spring boot·后端·nginx
一丝晨光1 小时前
Web技术简史、前后端分离、游戏
前端·javascript·css·游戏·unity·前后端分离·cocos
假客套1 小时前
2024 uniapp入门教程 01:含有vue3基础 我的第一个uniapp页面
前端·uni-app·vue3·hbuilder x
柒小毓1 小时前
网站开发基础:HTML、CSS
前端·css·html
&白帝&3 小时前
Vue.js 过渡 & 动画
前端·javascript
总是学不会.3 小时前
SpringBoot项目:前后端打包与部署(使用 Maven)
java·服务器·前端·后端·maven
Fanfffff7204 小时前
深入探索Vue3组合式API
前端·javascript·vue.js
光影少年4 小时前
node配置swagger
前端·javascript·node.js·swagger
昱禹4 小时前
关于CSS Grid布局
前端·javascript·css