使用
react
已经有不短的时间了,最近看到关于
react
高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡、一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低的东西如今可不多见了啊,是个不可多得的
zhuangbility
的利器,自然不可轻易错过,遂深入了解了一番。
概述
高阶组件的定义
React 官网上对高阶组件的定义:
高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从
React
演化而来的一种模式。 具体地说,高阶组件就是一个接收一个组件并返回另外一个新组件的函数。 相比于普通组件将
props
转化成界面UI,高阶组件将一个普通组件转化为另外一个组件。
大概意思就是说,
HOC
并不是
react
``
API
的一部分,而是一种实现的模式,有点类似于
观察者模式
、
单例模式
之类的东西,本质还是函数。
功能
既然是能够拿来
zhuangbility
的利器,那么不管怎么说,简单实用的招式必不可少,可以利用高阶组件来做的事情:
-
代码复用,逻辑抽象,抽离底层准备(
bootstrap
)代码
-
Props
更改
-
State
抽象和更改
-
渲染劫持
用法示例
基本用法
// HOCComponent.js
import React from 'react'
export
default PackagedComponent =>
class HOC extends React.Component {
render() {
return (
<div id="HOCWrapper">
Titleh1>
header>
<PackagedComponent/>
div>
)
}
}
此文件导出了一个函数,此函数返回经过一个经过处理的组件,它接受一个参数
PackagedComponent
,此参数就是将要被
HOC
包装的普通组件,接受一个普通组件,返回另外一个新的组件,很符合高阶组件的定义。
// main.js
import React from 'react'
// (1)
import HOCComponent from './HOCComponent'
// (2)
@HOCComponent
class
Main extends React.Component {
render() {
return(
main contentp>
main>
)
}
}
// (2)
// 也可以将上面的 @HOCComponent换成下面这句
// const MainComponent = HOCComponent(Main)
export default MainComponent
想要使用高阶组件,首先
(
1
)
将高阶组件导入,然后
(
2
)
使用此组件包装需要被包装的普通组件
Main
,这里的
@
符号是
ES7
中的
decorator
,写过
Java
或者其他静态语言的同学应该并不陌生,这实际上就是一个语法糖,可以使用 react-decorators 进行转换, 在这里相当于下面这句代码:
const MainComponent = HOCComponent(Main)
@HOCComponent
完全可以换成上面那句,只不过需要注意的是,类不具有提升的能力,所以若是觉得上面那句顺眼换一下,那么在换过之后,还要将这一句的位置移到类
Main
定义的后面。
最后,导出的是被高阶组件处理过的组件
MainComponent
import React from 'react'
import { render } from 'react-dom'
// 导入组件
import MainComponent from './main'
render(
<MainComponent
/>,
document.getElementById('root')
)
页面显示如下:
可以使用
React
Developer
Tools
查看页面结构:
可以看出,组件
Main
的外面包装了一层
HOC
,有点类似于父组件和子组件,但很显然高阶组件并不等于父组件。
另外需要注意的一点,
HOC
这个高阶组件,我们可能会用到不止一次,功能技术上没什么关系,但是不利于调试,为了快速地区分出某个普通组件的所属的
HOC
到底是哪一个,我们可以给这些
HOC
进行命名:
// 获取传入的被包装的组件名称,以便为 HOC 进行命名
let getDisplayName = component => {
return component.displayName || component.name || 'Component'
}
export default PackagedComponent =>
class HOC extends React.Component {
// 这里的 displayName就指的是 HOC的显示名称,我们将它重新定义了一遍
// static被 stage-0 stage-1 和 stage-2所支持
static displayName = `HOC(${getDisplayName(PackagedComponent)})`
render() {
return (
<div id=
"HOCWrapper">
Titleh1>
header>
<PackagedComponent/>
div>
)
}
}
现在的 DOM结构:
可以看到,原先的
HOC
已经变成了
HOC
(
Main
)
了,这么做主要是利于我们的调试开发。
这里的
HOC
,可以看做是一个简单的为普通组件增加
Title
的高阶组件,但是很明显并不是所有的页面都只使用同一个标题,标题必须要可定制化才符合实际情况。
想做到这一点也很简单,那就是再为
HOC
组件的高阶函数增加一个
title
参数,另外考虑到
柯里化
Curry
函数和函数式编程,我们修改后的
HOC
代码如下:
// HOCComponent.js
// 增加了一个函数,这个函数存在一个参数,此参数就是要传入的`title`
export default PackagedComponent => componentTitle =>
class HOC extends React.Component {
static displayName = `HOC(${getDisplayName(PackagedComponent)})`
render() {
return (
<div id="HOCWrapper">
{
componentTitle ? componentTitle : 'Title' }h1>
header>
<PackagedComponent/>
div>
)
}
}
使用方式如下:
// main.js
// ...省略代码
const MainComponent = HOCComponent(Main)('首页')
export default MainComponent
然后在页面上就可以看到效果了:
属性代理
HOC
是包裹在普通组件外面的一层高阶函数,任何要传入普通组件内的
props
或者
state
首先都要经过
HOC
。
props
和
state
等属性原本是要流向 目标组件的腰包的,但是却被 雁过拔毛的
HOC
拦路打劫,那么最终这些
props
和
states
数据到底还能不能再到达 目标组件,或者哪些能到达以及到达多少就全由
HOC
说了算了,也就是说,
HOC
拥有了提前对这些属性进行修改的能力。
更改
Props
对
Props
的更改操作包括
增、删、改、查
,在修改和删除
Props
的时候需要注意,除非特殊要求,否则最好不要影响到原本传递给普通组件的
Props
class HOC extends React.Component {
static displayName = `HOC(${getDisplayName(PackagedComponent)})`
render
() {
// 向普通组件增加了一个新的 `Props`
const newProps = {
summary: '这是内容'
}
return (
<div id="HOCWrapper">
{ componentTitle ? componentTitle : 'Title' }h1>
header>
<PackagedComponent {...
this.props} {...newProps}/>
div>
)
}
}
通过
refs
获取组件实例
普通组件如果带有一个
ref
属性,当其通过
HOC
的处理后,已经无法通过类似
this
.
refs
.
component
的形式获取到这个普通组件了,只会得到一个被处理之后的组件,想要仍然获得原先的普通组件,需要对
ref
进行处理,一种处理方法类似于
react
-
readux
中的
connect
方法,如下:
// HOCComponnet.js
...
export default PackagedComponent => componentTitle =>
class HOC extends React.Component {
static displayName = `HOC(${getDisplayName(PackagedComponent)})`
// 回调方法,当被包装组件渲染完毕后,调用被包装组件的 changeColor 方法
propc(wrapperComponentInstance) {
wrapperComponentInstance.changeColor()
}
render() {
// 改变 props,使用 ref 获取被包装组件的示例,以调用其中的方法
const props = Object.assign({}, this.props, {ref: this.propc.bind(this)})
return (
<div id="HOCWrapper">
{ componentTitle ? componentTitle : 'Title' }h1>
header>
<PackagedComponent {...props}/>
div>
)
}
}
使用:
// main.js
...
class Main extends React.Component {
render() {
return(
main contentp>
{ this.props.summary }span>
main>
)
}
// main.js 中的changeColor 方法
changeColor() {
console.log(666);
document.querySelector('p').style.color = 'greenyellow'