【React】如何使用高阶组件实现代码复用

191 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

本文主要内容:用最基础的例子构建和使用高阶组件,进而对高阶组件有一个最初步的了解

昨天去图书馆借第四版的红宝书来看,拿到手时865页的厚重分量让我意识到,必须要有其他学习任务和阅读他并行才是,所以我打消了完全掌握 js 后再继续学习 react 的想法,又回到了 scrimba 进行 advanced react 的学习。

名词解释

高阶组件 Higher Ordered Components 也即 HOCs

在我的理解中,虽然叫做高阶组件,但是事实上它是一个以组件为参数,返回值也是组件的函数。

就像它的命名往往是withXXX.js,高阶组件这个函数的意义就是加工传入的组件,生成内容、功能更丰富的组件,也即给现有的组件添加上一些东西。

做一个最简单的例子,我们想给App组件上添加一行语句 "hello world"

方法一

这事相当简单,如下代码轻松完成,没有意外

export default function App() {
  return (
    <div>
      hello world
    </div>
  )
}

方法二

不过我们的目标不是让 hello world 直接出现在 App.js 的代码中,于是想到了可以用 props ,所以可以写出以下代码

export default function App(props) {
  return (
    <div>
      {props.text}
    </div>
  )
}

而这个props从index.js中传入便是 <App text = {"hello world"}/>

方法三

但是这还是没有涉及高阶组件,接下来我们希望 index.js 中的组件不变,依旧是传入整体的<App />

这就用到了高阶组件,首先创建一个 HOCs ,起名withText.js,编写如下代码

import React from "react";

export function withText(component){
    const Component = component;
    return function(props) {
        return (
            <Component text = {"hello world"} {...props} />
        )
    }
}

可以看到我们编写了一个withText()函数,接收了一个组件,输出的也是一个组件,不过增加了一个 props ,即text = {"hello world"}

所以再回到 App.js 中,编写如下代码

import React from "react"
import { withText } from "./withText"

function App(props) {
  return (
    <div>
      {props.text}
    </div>
  )
}

export default withText(App)

可以看到我们输出的 HOC 没有用 default ,所以需要加上 {} 来引用,最后输出的组件看似是 App ,实际上已经是被 withText() 加工后的 App 了,这样我们就实现了不在 index.js 中传入 props ,也能使用 props 了。

实现代码复用

了解了什么是高阶组件后,我们就要进一步地用到它优雅的地方

也就是将组件中相同的部分提取出来作为高阶组件,然后再通过使用高阶组件来传递这一部分,实现同一段代码的复用。

项目预览与分析

所以让我们先来预览一下这个项目

image.png

可以看到有两个点击的地方,上面的按钮能够通过点击隐藏或展示信息,下面的按钮则会根据点击转换形态

而这也就意味着有两段相同或者类似的代码存在,以其中Favorite.js的代码为例

import React, { Component } from "react"

class Favorite extends Component {
    state = {
        isFavorited: false
    }
    
    toggleFavorite = () => {
        this.setState(prevState => {
            return {
                isFavorited: !prevState.isFavorited
            }
        })
    }
    
    render() {
        return (
            <div>
                <h3>Click heart to favorite</h3>
                <h1>
                    <span 
                        onClick={this.toggleFavorite}
                    >
                        {this.state.isFavorited ? "❤️" : "♡"}
                    </span>
                </h1>
            </div>
        ) 
    }
}

export default Favorite

可以看到state和toggle就是可以通过编写高阶组件进行复用的

创建高阶组件

在src下新建一个文件夹HOCs用以区分普通组件和高阶组件,然后再在这个文件夹下新建文件withToggler.js,里面写的函数就是我们要写的高阶组件,则可以得到以下框架

import React from "react"

export function withToggler(component) {
    return function(props) {
        return (
            <Toggler component={component} {...props}/>
        )
    }
}

可以看到写了一个返回<Toggler />组件的函数

调用高阶组件

以 Favorite.js 为例,

首先需要引入高阶组件,使用以下代码

import {withToggler} from "./HOCs/withToggler"

然后需要使用这个引入的函数加工我们要输出的组件,使用以下代码

export default withToggler(Favorite)

这样我们就完成了高阶组件的调用

编写高阶组件

现在要回过头来正式编写高阶组件函数功能的实现了,因为我们输出的是一个<Toggler />组件,所以我们需要在js文件中创建它

class Toggler extends Component {
    
}

然后在这个框架下对这个组件进行状态和函数的编写

state = {
    on: false
}

toggle = () => {
    this.setState(prevState => {
        return {
            on: !prevState.on
        }
    })
}

设置state中的属性,on: false

并且编写了一个可以转换这个属性的函数toggle()

完成了需要的功能和状态的编写后,接下来就是要将Toggler组件render成什么样,代码如下

render() {
    const Component = this.props.component
    return (
        <Component on={this.state.on} toggle={this.toggle} {...this.props}/>
    )
}

可以看到接收了传入的component,传入了on、toggle以及其他可能存在的props

这样我们就完成了组件的转换,将传入的component最终化成了Toggler

应用高阶组件

还是以 Favorite.js 为例,

第一步我们就可以先把已经在withToggler.js中写好的state和toggleFavorite删掉了

第二步要修改render()中的调用,比如onClick={this.toggleFavorite}中,toggleFavorite已经被删除了,所以我们要使用onClick={this.props.toggle}

同理修改{this.props.on ? "❤️" : "♡"}

这样保存运行,发现程序一切正常

这时我们还可以将这个class component转化为function component

先将声明改为function Favorite(props) ,然后删除render(),同时引用的来自react的{Component}也就不再需要了

所以我们可以得到最终的应用高阶组件后的代码

import React from "react"
import {withToggler} from "./HOCs/withToggler"

function Favorite(props) {
    return (
        <div>
            <h3>Click heart to favorite</h3>
            <h1>
                <span 
                    onClick={props.toggle}
                >
                    {props.on ? "❤️" : "♡"}
                </span>
            </h1>
        </div>
    ) 
}

export default withToggler(Favorite)

运行后能获得正常的效果,另一个Menu组件也能由这个高阶组件转化获得点击toggle的效果,这就实现了一段代码的复用