本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
本文主要内容:用最基础的例子构建和使用高阶组件,进而对高阶组件有一个最初步的了解
昨天去图书馆借第四版的红宝书来看,拿到手时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 了。
实现代码复用
了解了什么是高阶组件后,我们就要进一步地用到它优雅的地方
也就是将组件中相同的部分提取出来作为高阶组件,然后再通过使用高阶组件来传递这一部分,实现同一段代码的复用。
项目预览与分析
所以让我们先来预览一下这个项目
可以看到有两个点击的地方,上面的按钮能够通过点击隐藏或展示信息,下面的按钮则会根据点击转换形态
而这也就意味着有两段相同或者类似的代码存在,以其中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的效果,这就实现了一段代码的复用