React学习笔记[12]✨~一文掌握高阶组件与其使用场景👻

84 阅读4分钟

我正在参加「掘金·启航计划」

一、什么是高阶组件

高阶组件是参数为组件,返回值为新组件的函数

也就是说,高阶组件不是一个组件,而是一个函数

这个函数的参数是一个组件,返回值也是一个组件

高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式,高阶组件在一些React第三方库中非常常见:

  • 比如Redux中的connect
  • 比如ReactRouter中的withRouter

示例:

比如,定义一个HelloWorld子组件,然后定义一个高阶组件,通过该高阶组件对HelloWorld子组件进行一些扩展

import React, { PureComponent } from 'react'

// 定义一个高阶组件
function hoc(Cpn) {
  class CpnNew extends PureComponent {
    render() {
      return (
        <div>
          <Cpn name="zs" />
          <h2 style={{color: this.props.color ? this.props.color: 'green'}}>hoc</h2>
        </div>
      )
    }
  }
  return CpnNew
}

class HelloWorld extends PureComponent {
  render() {
    return <h1>Hello World</h1>
  }
}
const HelloWorldNew = hoc(HelloWorld)
export class App extends PureComponent {
  render() {
    return (
      <HelloWorldNew color="red"/>
    )
  }
}

export default App

在hoc高阶组件中我们也可以返回一个新的函数式组件:

function hoc(Cpn) {
    // 返回一个函数组件
    function CpnNew(props) {
      return(
        <div>
          <Cpn name="zs" />
          <h2 style={{color: props.color ? props.color : 'green'}}>funcion</h2>
        </div>
      )
    }
    return CpnNew
}

二、高阶组件的使用场景

场景一:props的增强

可以利用高阶组件,在不修改原有代码的情况下,添加新的props

比如,定义一个enhancedUserInfo高阶组件,为传递进来的组件统一增加userInfo信息

hoc/enhancedUserInfo:

import { PureComponent } from "react";

// 定义组件: 给一些需要特殊数据的组件, 注入props
function enhancedUserInfo(OriginComponent) {
  class NewComponent extends PureComponent {
    constructor(props) {
      super(props);

      this.state = {
        userInfo: {
          name: "zhangsan",
          level: 100,
        },
      };
    }

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

  return NewComponent;
}

export default enhancedUserInfo;

在enhancedUserInfo中,返回传递的原始组件基础上,为原始组件添加传递给新组件的props以及在新组件中定义的userInfo作为增强后的props(增强后的props = props + userInfo)

App.jsx:

import React, { PureComponent } from 'react'
import enhancedUserInfo from './hoc/enhanced_props'


const Home = enhancedUserInfo(function(props) {
  console.log('props...', props)
  return (
    <div>
      轮播图:{props.banners}
      <h1>name: {props.name}</h1>
      <h2>level: {props.level}</h2>
    </div>
  )
})

const Profile = enhancedUserInfo(function(props) {
  return <h1>Profile: {props.name}-{props.level}</h1>
})

export class App extends PureComponent {
  render() {
    return (
      <div>
        <Home banners={["轮播1", "轮播2"]}/>
        <Profile/>
      </div>
    )
  }
}

export default App

场景二:Context共享

比如,在App.jsx中使用创建好的ThemeContext上下文为子组件ProductA与ProductB共享了主题数据,若在子组件中使用,可通过ThemeContext中提供的Consumer来获取:

context/ThemeContext.js:

import { createContext } from "react"
const ThemeContext = createContext()
export default ThemeContext

App.jsx:

import React, { PureComponent } from 'react'
import ThemeContext from './context/theme_context'
import ProductA from './pages/ProductA'
import ProductB from './pages/ProductB'

export class App extends PureComponent {
  render() {
    return (
      <div>
        <ThemeContext.Provider value={{color: "red", size: 30}}>
          <ProductA />
          <ProductB />
        </ThemeContext.Provider>
      </div>
    )
  }
}

export default App

ProductA.jsx:

import React, { PureComponent } from 'react'
import ThemeContext from '../context/theme_context'

export default class ProductA extends PureComponent {
  render() {
    return (
      <div>
        <h1>ProductA:</h1>
        <ThemeContext.Consumer>
          {
            value => {
              return <h2>theme:{value.color}-{value.size}</h2>
            }
          }
        </ThemeContext.Consumer>
      </div>
    )
  }
}

ProductB.jsx:

import React, { PureComponent } from 'react'
import ThemeContext from '../context/theme_context'
export default class ProductB extends PureComponent {
  render() {
    return (
      <div>
        <h1>ProductB:</h1>
        <ThemeContext.Consumer>
          {
            value => {
              return <h2>theme:{value.color}-{value.size}</h2>
            }
          }
        </ThemeContext.Consumer>
      </div>
    )
  }
}

可见,在子组件中均需要编写关于<ThemeContext.Consumer>的逻辑

此时可以考虑采用高阶组件为子组件进行扩展,将Context上下文中的数据放入子组件的props中

这样,在扩展后的子组件中,便可以通过props来获取到下上文中的主题信息了

写一个高阶组件,在此高阶组件中,为传递进来的子组件进行扩展,将上下文中的主题信息扩展至props中:

hoc/with_theme.js:

import ThemeContext from "../context/theme_context";

function withTheme(OriginComponent) {
  return (props) => {
    return (
      <ThemeContext.Consumer>
        {(value) => {
          return <OriginComponent {...value} {...props} />;
        }}
      </ThemeContext.Consumer>
    );
  };
}

export default withTheme;

ProductA:

import React, { PureComponent } from 'react'
import withTheme from '../hoc/with_theme'
export class ProductA extends PureComponent {
  render() {
    return (
      <div>
        <h1>ProductA:</h1>
        <h2>theme: {this.props.color}-{this.props.size}</h2>
      </div>
    )
  }
}

export default withTheme(ProductA)

ProductB:

import React, { PureComponent } from 'react'
import withTheme from '../hoc/with_theme'
export class ProductB extends PureComponent {
  render() {
    return (
      <div>
        <h1>ProductB:</h1>
        <h2>theme: {this.props.color}-{this.props.size}</h2>
      </div>
    )
  }
}

export default withTheme(ProductB)

场景三:登录鉴权

比如App中引入了子组件Cart,而Cart中的内容比如在登录之后才能进行查看,此时可抽取出一个高阶组件:

  • 该高阶组件中判断是否已经登录
  • 如果已经登录则返回原组件
  • 如果为登录则展示文字“请登录”

App.jsx:

import React, { PureComponent } from 'react'
import Cart from './pages/Cart'

export class App extends PureComponent {
  login() {
    localStorage.setItem('token', '123')
    this.forceUpdate()
  }
  render() {
    return (
      <div>
        <button onClick={e => this.login()}>点我登录</button>
        <Cart />
      </div>
    )
  }
}

export default App

hoc/login_auth.js:

function loginAuth(OriginComponent) {
  return (props) => {
    const token = localStorage.getItem("token");
    if (token) {
      return <OriginComponent {...props} />;
    } else {
      return <h2>请先登录哦~</h2>;
    }
  };
}

export default loginAuth;

pages/Cart.jsx:

import React, { PureComponent } from 'react'
import loginAuth from '../hoc/login_auth'

export class Cart extends PureComponent {
  render() {
    return (
      <h2>Cart Page</h2>
    )
  }
}

export default loginAuth(Cart)

场景四:嵌入公共的生命周期

比如,在各个组件中,需要打印出组件挂载所耗费的时间

  • 那么此时可以抽取出一个高阶组件,在此高阶组件中返回一个包装好的类组件,在写入生命周期

hoc/log_render_time.js:

import { PureComponent } from "react";

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}/>
    }
  }
}

export default logRenderTime

Detail.jsx:

import React, { PureComponent } from 'react'
import logRenderTime from '../hoc/log_render_time'

export class Detail extends PureComponent {
  render() {
    return (
      <div>
        <h2>Detail Page</h2>
        <ul>
          <li>数据列表1</li>
          <li>数据列表2</li>
          <li>数据列表3</li>
          <li>数据列表4</li>
          <li>数据列表5</li>
          <li>数据列表6</li>
          <li>数据列表7</li>
          <li>数据列表8</li>
          <li>数据列表9</li>
          <li>数据列表10</li>
        </ul>
      </div>
    )
  }
}

export default logRenderTime(Detail)

App.jsx:

import React, { PureComponent } from 'react'
import Detail from './pages/Detail'

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

export default App