一、简介
高阶组件是一个可以抽象多个组件中共同功能的一种方式,高阶组件其实就是一个函数,接受一个普通组件作为参数,然后我们经过一些处理包装,返回这个被包装的中间组件,类似于JAVA中的装饰模式。
高阶组件主要有两种实现方式:
a. 属性代理(Props Proxy): 高阶组件通过wrappedComponent的props来进行相关操作;
b. 继承反转(Inheritance Inversion): 高阶组件继承自wrappedComponent;
下面我们分别对两种方式做一个简单的介绍。
二、属性代理
属性代理的方式主要有一下几点功能:
【a】 操作props: 可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件;
【b】 通过refs访问组件实例并调用组件的方法: ref={componentInstance => this.componentInstance = componentInstance};
【c】提取state: 如下示例的onClickListener、count回调给普通组件实现简单计数功能;
【d】用其他元素包裹WrappedComponent, 实现布局等目的: 如给表格最外层加入滚动条等;
下面通过一个示例说明以上四点功能:
MyApp.js:
import React from 'react';
import {Card} from 'antd';
import {wrappedComponent} from './WrapComponent';
/**
* @Description: 普通组件
* @author weixiaohuai
* @date 2019/5/15 15:15
*/
class MyApp extends React.Component {
componentDidMount() {
//基础组件中访问高阶组件扩展的属性
const {name, age, count} = this.props;
console.log('name', name);
console.log('age', age);
console.log('count', count);
}
sayHello = () => {
console.log('使用ref获取组件实例,调用组件方法');
};
render() {
const {onClickListener, count} = this.props;
return (
<div>
<Card title="高阶组件(属性代理(Props Proxy))的简单示例" extra={<a onClick={onClickListener}>点击</a>}
style={{width: 300}}>
<p>一共点击了{count}次</p>
</Card>
</div>
);
}
}
//返回高阶函数处理之后的一个实例,其实就是高阶组件中定义的中间组件
export default wrappedComponent(MyApp);
WrapComponent.js:
import React from 'react';
import {Alert} from 'antd';
/**
* 高阶函数: 将普通组件作为参数,然后经过一些逻辑处理后返回一个新的组件
*
* @param Component 普通组件
* 基础组件作为高阶组件的参数传入
*
* 说明: 高阶组件是一个实现抽象组件公共功能的好方法。高阶组件其实是一个函数,接收一个组件作为参数,
返回一个包装组件作为返回值,类似于高阶函数。高阶组件和装饰器就是一个模式,因此,高阶组件可以作为
装饰器来使用
*
* 属性代理(Props Proxy)
1. 操作props: 可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件;
2. 通过refs访问组件实例并调用组件的方法: ref={componentInstance => this.componentInstance = componentInstance};
3. 提取state: 如下示例的onClickListener、count回调给普通组件实现简单计数功能;
4. 用其他元素包裹WrappedComponent, 实现布局等目的: 如给表格最外层加入滚动条等;
*/
export const wrappedComponent = (Component) => {
//创建一个中间组件,该中间组件会在添加了逻辑之后返回
return class MyApp2 extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'weixiaohuai',
age: 20,
count: 0
}
}
componentDidMount() {
let currentComponentInstance = this.componentInstance;
//获取当前组件实例
console.log('当前组件实例--> ', currentComponentInstance);
//调用当前组件的方法
const {sayHello} = currentComponentInstance;
if (sayHello && typeof sayHello === 'function') {
sayHello();
}
}
onClickListener = () => {
const {count} = this.state;
this.setState({
count: count + 1
});
};
render() {
return (
<div>
{/*高阶组件为基础组件中扩展新的props*/}
<Component {...this.props} {...this.state} onClickListener={this.onClickListener}
ref={componentInstance => this.componentInstance = componentInstance}>高阶组件</Component>
{/*使用div包裹WrappedComponent,扩展一些布局组件、样式等*/}
<Alert message="Success Text" type="success"/>
</div>
)
}
}
};
三、继承反转
继承反转的方式主要有一下几点功能:
【a】渲染劫持: 可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容, 例如给表格动态增加一列/给原组件添加一些数据等等
【b】 通过继承WrappedComponent,我们可以获取除了一些静态方法,包括生命周期,state,各种function等 (this.state/this.props),示例如WrappedComponent3.js
【c】条件渲染: 可以劫持当前渲染的普通组件中的内容,动态控制返回内容/或者包裹普通组件再进行返回; 示例如WrappedComponent4.js
下面通过几个示例分别说明以上功能:
a. 渲染劫持: 为原组件中添加一些数据,然后渲染新的数据;
import React from 'react';
/**
*
* @param Component 普通组件
* 基础组件作为高阶组件的参数传入
*
* 继承反转(Inheritance Inversion)
*
* 1. 渲染劫持: 可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容, 例如给表格动态增加一列/给原组件添加一些数据等等
* 2. 通过继承WrappedComponent,我们可以获取除了一些静态方法,包括生命周期,state,各种function等 (this.state/this.props),示例如WrappedComponent3.js
* 3. 条件渲染: 可以劫持当前渲染的普通组件中的内容,动态控制返回内容/或者包裹普通组件再进行返回; 示例如WrappedComponent4.js
*/
export const wrappedComponent = (Component) => {
return class extends Component {
render() {
//为原组件中添加一些数据,然后渲染新的数据
const elementTree = super.render();
console.log('MyApp2 ---elementTree', elementTree);
const {children} = elementTree.props;
console.log('children', children);
let {dataSource} = children.props;
//[{title: "Ant Design Title 1"},{title: "Ant Design Title 2"},{title: "Ant Design Title 3"},{title: "Ant Design Title 4"}]
console.log('dataSource', dataSource);
dataSource.push({
title: 'Ant Design Title 5',
});
dataSource.push({
title: 'Ant Design Title 6',
});
//克隆组件,然后返回
return React.cloneElement(elementTree);
}
}
};
import React from 'react';
import {wrappedComponent} from './WrapComponent2';
import {Avatar, List} from 'antd';
/**
* @Description:
* @author weishihuai
* @date 2019/5/16 11:28
*/
@wrappedComponent
class MyApp2 extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'weixiaohuai'
}
}
componentDidMount() {
console.log('componentDidMount');
}
render() {
const data = [
{
title: 'Ant Design Title 1',
},
{
title: 'Ant Design Title 2',
},
{
title: 'Ant Design Title 3',
},
{
title: 'Ant Design Title 4',
},
];
return (
<div>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={item => (
<List.Item>
<List.Item.Meta
avatar={<Avatar
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"/>}
title={<a href="https://ant.design">{item.title}</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
)}
/>
</div>
);
}
}
export default MyApp2;
b. 访问包括生命周期,state,各种function等 (this.state/this.props);
WrapComponent3.js:
import React from 'react';
export const wrappedComponent = (Component) => {
return class extends Component {
render() {
console.log('this.props', this.props);
console.log('this.state', this.state);
return super.render();
}
}
};
import React from 'react';
import {wrappedComponent as wrappedComponent3} from './WrapComponent3'
import {Avatar, List} from 'antd';
/**
* @Description:
* @author weishihuai
* @date 2019/5/16 11:28
*/
@wrappedComponent3
class MyApp2 extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'weixiaohuai'
}
}
componentDidMount() {
console.log('componentDidMount');
}
render() {
const data = [
{
title: 'Ant Design Title 1',
},
{
title: 'Ant Design Title 2',
},
{
title: 'Ant Design Title 3',
},
{
title: 'Ant Design Title 4',
},
];
return (
<div>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={item => (
<List.Item>
<List.Item.Meta
avatar={<Avatar
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"/>}
title={<a href="https://ant.design">{item.title}</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
)}
/>
</div>
);
}
}
export default MyApp2;
c. 条件渲染:可以劫持当前渲染的普通组件中的内容,动态控制返回内容/或者包裹普通组件再进行返回;
WrapComponent4.js:
import React from 'react';
export const wrappedComponent = isShow => (Component) => {
return class extends Component {
render() {
return isShow ? super.render() : '暂无内容';
}
}
};
import React from 'react';
import {wrappedComponent as wrappedComponent4} from './WrapComponent4'
import {Avatar, List} from 'antd';
@wrappedComponent4(false)
class MyApp2 extends React.Component {
render() {
const data = [
{
title: 'Ant Design Title 1',
},
{
title: 'Ant Design Title 2',
},
{
title: 'Ant Design Title 3',
},
{
title: 'Ant Design Title 4',
},
];
return (
<div>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={item => (
<List.Item>
<List.Item.Meta
avatar={<Avatar
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"/>}
title={<a href="https://ant.design">{item.title}</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
)}
/>
</div>
);
}
}
export default MyApp2;
四、高阶组件传参方式
直接上一个案例说明一下高阶组件怎么传参数:
MyTable01.js:
import React from 'react';
import {Divider, Table, Tag} from 'antd';
import {wrappedComponent} from "./WrapComponent";
@wrappedComponent(true, false, undefined)
class MyTable01 extends React.Component {
render() {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: text => <a href="javascript:;">{text}</a>,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: tags => (
<span>
{tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</span>
),
},
{
title: 'Action',
key: 'action',
render: (text, record) => (
<span>
<a href="javascript:;">Invite {record.name}</a>
<Divider type="vertical"/>
<a href="javascript:;">Delete</a>
</span>
),
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
return (
<div>
<Table columns={columns} dataSource={data} {...this.props}/>
</div>
);
}
}
export default MyTable01;
MyTable02.js:
import React from 'react';
import {Button, Divider, Table, Tag} from 'antd';
import {wrappedComponent} from "./WrapComponent";
const {Column, ColumnGroup} = Table;
@wrappedComponent(true, true, () => <div>Here is footer <Button>xxx</Button></div>)
class MyTable02 extends React.Component {
render() {
const data = [
{
key: '1',
firstName: 'John',
lastName: 'Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
firstName: 'Jim',
lastName: 'Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
firstName: 'Joe',
lastName: 'Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
return (
<div>
<Table dataSource={data} {...this.props}>
<ColumnGroup title="Name">
<Column title="First Name" dataIndex="firstName" key="firstName"/>
<Column title="Last Name" dataIndex="lastName" key="lastName"/>
</ColumnGroup>
<Column title="Age" dataIndex="age" key="age"/>
<Column title="Address" dataIndex="address" key="address"/>
<Column
title="Tags"
dataIndex="tags"
key="tags"
render={tags => (
<span>
{tags.map(tag => (
<Tag color="blue" key={tag}>
{tag}
</Tag>
))}
</span>
)}
/>
<Column
title="Action"
key="action"
render={(text, record) => (
<span>
<a href="javascript:;">Invite {record.lastName}</a>
<Divider type="vertical"/>
<a href="javascript:;">Delete</a>
</span>
)}
/>
</Table>
</div>
);
}
}
export default MyTable02;
WrapComponent.js:
import React from 'react';
/**
* @Description: 高阶函数 - 传参
* @author weishihuai
* @date 2019/5/15 16:30
*
* 说明: 根据需要对项目中的表格进行定制,如果有非常多的表格,就可以不用每个表格都需要去修改,直接将组件使用高阶组件进行加强即可,比较方便
*
* isShowBordered: 表格是否需要边框
* isShowFooter: 是否展示表格尾部
* footerContainer: 表格尾部容器
* Component: 待包装加强的普通组件
*
* 属性代理(Props Proxy)
*/
export const wrappedComponent = (isShowBordered, isShowFooter, footerContainer) => (Component) => {
return class extends React.Component {
render() {
const newProps = {
bordered: isShowBordered,
footer: isShowFooter ? footerContainer : undefined
};
return (
<Component {...this.props} {...newProps}/>
)
}
}
};
通过下面的方式进行传参数:
运行结果:可以看到:MyTable01以及MyTable02都有边框,并且MyTable02多了一个底部自定义容器。
MyTable01:
MyTable02:
五、总结
本文对React中的高阶组件的两种方式:"属性代理以及反向继承" 做了一个简单的介绍,并且通过一些示例说明了其用法.实际工作中,需要具体的需求创建高阶组件,这样有利于业务之间的解耦,每个高阶组件都定义自己的处理逻辑。这里只是简单地介绍了HOC的使用方法,并不是高阶组件的全部用法。大家可以结合实际的应用场景,尝试不一样的用法。