高阶组件的概念
维基百科中表明高阶组件的定义至少要满足两个条件: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 官网查阅相关资料,也是有很多干货的。