React的高阶函数

57 阅读4分钟

高阶函数

高阶函数的维基百科定义:至少满足以下条件之一

  • 接受一个或多个函数作为输入
  • 输出一个函数
  • JavaScript中比较常见的filter、map、reduce都是高阶函数

高阶组件

高阶组件的英文是 Higher-Order Components,简称为 HOC。官方的定义:高阶组件是参数为组件,返回值为新组件的函数

我们可以进行如下的解析:

  • 首先, 高阶组件本身不是一个组件,而是一个函数
  • 其次,这个函数的参数是一个组件,返回值也是一个组件

我们来看个案例,类似这样。 image.png 函数接收组件得到一个新的组件。接收到的变量是个函数。

下面就是一个高阶组件 image.png

我们在浏览器中查看 image.png

如果我们想让名字更灵活一些。

image.png

在ES6中,如果我们是一个类表达式,类名可以不写。函数也是一样的。

我们简化成这样 image.png 整个样子我们去浏览器看一下(Anoymous是匿名,不知道的意思) image.png

我们也可要自己定义名字(用的比较少) image.png

如果我们要传入属性。记得在高阶组件处获取。 image.png

image.png

我们也可以在高阶组件中定义函数式组件。 image.png

高阶组件不是React的API,它其实是一种设计模式。

高阶组件并不是React API的一部分,它是基于React的 组合特性而形成的设计模式。高阶组件在一些React第三方库中非常常见。比如redux中的connect。比如react-router中的withRouter。

高阶组件的作用(应用场景)

从上面的例子我们很容易发现,高阶组件的作用之一就是劫持。 我们具体来演示一下,它的作用。

应用一:增强props

image.png 如果我们想给它们都新增一个属性比如说region,那么我每个都要这样写。

image.png 这样我们就可以定义一个高阶组件。

import React, { PureComponent } from 'react'

//定义一个高阶组件
function enhanceRegionProps(WrappedComponent){
  return props => { //返回函数式组件
    return <WrappedComponent {...props} region = "China"></WrappedComponent>
  }
}

class Home extends PureComponent{
  render(){
    return(
      <h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
    )
  }
}

const EnhanceHome = enhanceRegionProps(Home)

class About extends PureComponent{
  render(){
    return(
      <h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
    )
  }
}
const EnhaceAbout = enhanceRegionProps(About)

export default class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname = "harry" level = {90}/>
        <EnhaceAbout nickname = "turman" level = {99}/>
      </div>
    )
  }
}

还有个增强的场景(在开发中运用的比较多): image.png 我们想通过Context来进行共享 image.png 有没有好办法呢?当然有,就是用高阶组件。

image.png

import React, { PureComponent,createContext } from 'react'

//定义一高阶组件
function withUser(WrappedComponent){
  return props => {
    return(
      <UserContext.Consumer>
      {
        user => {
          return  <WrappedComponent {...props} {...user}></WrappedComponent>
        }
      }
     </UserContext.Consumer>
    )
  }
}

//创建Context
const UserContext= createContext({
  nickname:'noname',
  level:-1,
  region:'China'
})

class Home extends PureComponent{
  render(){
    return(
     <UserContext.Consumer>
      {
        user => {
          return  <h2>Home:{`昵称:${user.nickname} 等级为:${user.level} 区域为:${user.region}`}</h2>
        }
      }
     </UserContext.Consumer>
    )
  }
}
class About extends PureComponent{
  render(){
    return(
      <h2>Home:{`昵称:${this.props.nickname} 等级为:${this.props.level} 区域为:${this.props.region}`}</h2>
    )
  }
}

const UserHome = withUser(Home) //使用高阶组件
const UserAbout = withUser(About)
export default class App extends PureComponent {
  render() {
    return (
      <div>
        <UserContext.Provider value={ {nickname:'harry',level:90,region:'Korean' } }>
          <UserHome />
          <UserAbout />
        </UserContext.Provider>   
      </div>
    )
  }
}

应用二:渲染判断鉴权

我们想实现这样一个场景,如果isLogin为true,就显示CartPage,反之显示LoginPage。也就是说我们要做鉴权操作。

如果每个页面都要做这种鉴权判断,非常不合适 image.png 我们可以这样做(这样也就是劫持jsx):

image.png

import React, { PureComponent } from 'react'

function withAuth(WrappedComponent){
  return props => {
    const {isLogin} = props
    if(isLogin){
      return <WrappedComponent {...props} />  
    } else {
      
      return <LoginPage></LoginPage>
    }
    
  }
}
class CartPage extends PureComponent{
  render(){
    return <h2>CartPage</h2>
  }
}
class LoginPage extends PureComponent {
  render() {
    return <h2>LoginPage</h2>
  }
}
const AuthCartPage = withAuth(CartPage)
export default class App extends PureComponent {
  render() {
    return (
      <div>
        <AuthCartPage isLogin = {true} />
      </div>
    )
  }
}

应用三:生命周期劫持

我们有这样一个需求。我们想知道把Home组件渲染出来花费的事件,并把它打印出来。

import React, { PureComponent } from 'react';

class Home extends PureComponent {
  //即将渲染获取一个时间 beginTime
  componentWillMount(){ //这个是即将挂载的意思,这个生命周期函数已经不用了
    this.beginTime = Date.now()
  }

  render() {
    return <h2>Home</h2>
  }

  //渲染完成再获取一个时间 endTime
  componentDidMount(){ //挂载完毕
    this.endTime = Date.now()
    const interval = this.endTime - this.beginTime;

    console.log(`Home渲染时间: ${interval}`)
  }
}


class About extends PureComponent {

  render() {
    return <h2>About</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <About />
      </div>
    )
  }
}

我们先用着,主要是为了案例演示。 image.png

如果我们想把相似的渲染时间都封装起来,早期可以使用mixin,React推荐使用高阶组件。现在已经转向hooks了。

我们采用高阶组件。

import React, { PureComponent } from 'react';

function withRenderTime(WrappedComponent) {
  return class extends PureComponent {
    componentWillMount() { //这个是即将挂载的意思,这个生命周期函数已经不用了
      this.beginTime = Date.now()
    }
    //渲染完成再获取一个时间 endTime
    componentDidMount() { //挂载完毕
      this.endTime = Date.now()
      const interval = this.endTime - this.beginTime;
        // 所有类都有.name属性,就是类的名字
      console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
    }
    render(){
      return <WrappedComponent {...this.props}></WrappedComponent>
    }

  }
}


class Home extends PureComponent {
  //即将渲染获取一个时间 beginTime


  render() {
    return <h2>Home</h2>
  }


}

class About extends PureComponent {

  render() {
    return <h2>About</h2>
  }
}
const TimeHome = withRenderTime(Home)
const TimeAbout = withRenderTime(About)
export default class App extends PureComponent {
  render() {
    return (
      <div>
        <TimeHome/>
        <TimeAbout />
      </div>
    )
  }
}

高阶函数的意义

我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理。

  • 其实早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用

    • Mixin 可能会相互依赖,相互耦合,不利于代码维护
    • 不同的Mixin中的方法可能会相互冲突
    • Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
  • 当然,HOC也有自己的一些缺陷

    • HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难
    • HOC可以劫持props,在不遵守约定的情况下也可能造成冲突
  • Hooks的出现,是开创性的,它解决了很多React之前的存在的问题

    • 比如this指向问题、比如hoc的嵌套复杂度问题等等