简介
React 高阶组件简单来说就是一个没有副作用的高阶纯函数,且该函数接受一个组件作为参数,并返回一个新的组件。正式由于他也算是高阶函数,所以也可以使用类似柯里化的方式传组件或者其他参数。常用于多个组件有共同方法的时候。
1.基础理解
假如现在有以下两个组件,分别是对当前用户进行说明
- 1.用户简介
// UserProfiles.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return (
<div>{this.state.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
)
}
}
export default UserProfiles;
复制代码
- 用户退出
// goodbueuser.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return (
<div>再见 {this.state.username}</div>
)
}
}
export default GoodbyeUser;
复制代码
可以看到代码的冗余量。按照平时我们的编码习惯,冗余的代码我们会将它封装成一个函数。高阶组件就类似这么一个函数的概念。好,下面我们尝试用高阶组件来对这两个组件封装
//hoc.js
import * as React from 'react'
import axios from 'axios';
export interface Iprops {
username: string;
}
export default <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
class NewComponent extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return <Com username={this.state.username} />
}
}
return NewComponent
}
}
复制代码
// UserProfiles.js
import React, { Component } from 'react';
import wrapHoc, { Iprops } from 'hoc';
class UserProfiles extends Component<Iprops> {
render() {
return (
<div>{this.props.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
)
}
}
UserProfilesCom = wrapHoc(UserProfiles);
export default UserProfilesCom;
复制代码
// goodbueuser.js
import * as React from 'react';
import wrapHoc, { Iprops } from 'hoc';
class GoodbyeUser extends React.Component<Iprops> {
render() {
return (
<div>再见 {this.props.username}</div>
)
}
}
GoodbyeUserCom = wrapHoc(GoodbyeUser);
export default GoodbyeUserCom;
复制代码
看到没有,高阶组件就是把username通过props传递给目标组件了。目标组件只管从props里面拿来用就好了。这样子就完成了一个基本的高阶组件了
2.更改props
可以对参数组件的props进行增删改查的操作。我们平时使用的时候更多的是增加的操作,比如说我们常用的 react-router-dom中的Withrouter()。上面的例子就是属于更改props
通过高阶组件,我们可以在参数组件中使用this.props.username来调用这个应用的用户名了,这就类似withrouter中使用this.props.match.params.xxxx,来获取路由参数一样。
3.抽象state
这种用法一般用于不可控组件。比如我们熟悉的input,在react中我们需要通过onChange事件将输入的值绑定到state上,然后通过触发render函数渲染UI才能体现出来。通过高阶组件,我们可以抽象一个state,将不可控组件转变成可控组件。
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.onNameChange = this.onNameChange.bind(this)
}
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
// 链接:https://www.jianshu.com/p/0aae7d4d9bc1
复制代码
然后这样使用它:(这里作者使用高阶组件的修饰器用法)
@ppHOC
class Example extends React.Component {
render() {
return <input name="name" {...this.props.name}/>
}
}
复制代码
4.更多高阶函数用法
前面提到了,高阶组件就是一个高阶函数,那么我们使用很多关于高阶函数的用法。比如说柯里化
import React from 'react';
import axios from 'axios';
export interface Iprops {
data: string;
}
const HocCom = (key: string) => <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
return class extends React.Component {
componentWillMount() {
axios.get('username/' + key).then(item => {
this.setState({
data: item.data.username
});
}).catch({
console.log('error')
})
}
render() {
return <Com data={this.state.data} />
}
}
}
class Com extends React.Component<Iprops> {
render() {
return <div>{this.props.data}</div>
}
}
export default HocCom('wenfei')(Com)
复制代码
我们常见的这种柯里化的模式就是在antd的表单模块中,我们一般都会在模块的结尾写上
const WrappedNormalLoginForm = Form.create()(NormalLoginForm);
复制代码
5.高阶组件使用注意事项
- 不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件
- 如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法
6.高阶组件、组件继承的区别
高阶组件、组件组合、组件继承都可以用来扩充组件,赋予组件新的能力。 react的官网 (reactjs.org/docs/compos…) 简单的说明了一下组件组合和继承的形式区别,而且在文章末尾建议我们不要使用继承的方式来扩充组件。为什么呢?我认为有以下几点。
1.灵活性
对于小项目,组件复用率小的情况下,其实三种方式都合适。但是对于大型的组件复用率高的项目,继承就会出现和明显的短板。比如说一个简单的List组件,通过继承方式获得可以支持上/下拉加载的组件ListA。有一天,项目经理说需要讲这个功能改成下拉刷新和向下滚动加载,发现ListA有部分功能相似,然后通过ListA继承修改代码获得了下拉刷新和向下滚动加载的组件ListB。第二天,项目经理又说要将功能做成仅仅滚动加载(例子,平时见不到这种需求)。what???我是要在原List组件上继承呢?还是在ListB上来继承呢?假如还有很多个list需要这种需求呢??都用来继承?这显得组件特别膨胀而且不灵活,耦合程度高。其实我们可以封装对应list的不同功能的高阶组件或组件组合,比如说下拉刷新的,向下滚动加载的,等等...然后拿来即用,传递想要功能的参数即可。灵活性大大提高。
2.单一性
还是上面的例子,我们发现我们通过继承获得了List\ListA\ListB等等组件。我们封装组件,一般是它实现了这个项目最基础而且可用的某个功能,相同功能引用相同组件即可。而继承的方式会获得基于这个基础组件的不同功能的组件,而且这些组件做的是同样事情,仅仅在方式上的不同。高阶组件/组件组合则可以保持这种react组件单一的特性。
3.复杂度
写组件继承,1.书写的麻烦,每个继承组件需要额外开销许多import和export。2.继承的时候还需要知道原组件实现了哪些方法?如果使用了ts还可能出现更多各种各样的错误。3.初始化 state、声明其它方法时要小心避免污染父类的state与方法。
参考文章
原文链接:tech.gtxlab.com/React-Hoc.h…
作者简介: 张栓,人和未来大数据前端工程师,专注于html/css/js的学习与开发。