【React】高阶组件学习总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Weixiaohuai/article/details/90484564

一、简介

高阶组件是一个可以抽象多个组件中共同功能的一种方式,高阶组件其实就是一个函数,接受一个普通组件作为参数,然后我们经过一些处理包装,返回这个被包装的中间组件,类似于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, 实现布局等目的: 如给表格最外层加入滚动条等;

下面通过一个示例说明以上四点功能:

扫描二维码关注公众号,回复: 6764220 查看本文章

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的使用方法,并不是高阶组件的全部用法。大家可以结合实际的应用场景,尝试不一样的用法。

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/90484564