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

相关推荐
转角羊儿7 分钟前
uni-app文章列表制作⑧
前端·javascript·uni-app
大G哥14 分钟前
python 数据类型----可变数据类型
linux·服务器·开发语言·前端·python
hong_zc37 分钟前
初始 html
前端·html
小小吱43 分钟前
HTML动画
前端·html
糊涂涂是个小盆友1 小时前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app
浮华似水1 小时前
Javascirpt时区——脱坑指南
前端
王二端茶倒水2 小时前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
_oP_i2 小时前
Web 与 Unity 之间的交互
前端·unity·交互
钢铁小狗侠2 小时前
前端(1)——快速入门HTML
前端·html
凹凸曼打不赢小怪兽2 小时前
react 受控组件和非受控组件
前端·javascript·react.js