React高阶组件学习总结

    xiaoxiao2022-07-13  138

    一、简介

    高阶组件是一个可以抽象多个组件中共同功能的一种方式,高阶组件其实就是一个函数,接受一个普通组件作为参数,然后我们经过一些处理包装,返回这个被包装的中间组件,类似于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的使用方法,并不是高阶组件的全部用法。大家可以结合实际的应用场景,尝试不一样的用法。

    最新回复(0)