我正在参加「掘金·启航计划」
一、什么是高阶组件
高阶组件是参数为组件,返回值为新组件的函数
也就是说,高阶组件不是一个组件,而是一个函数
这个函数的参数是一个组件,返回值也是一个组件
高阶组件并不是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