React 高阶组件研究

React 高阶组件(HOC)

一.概念

高阶组件是一个纯函数,也是容器组件.是参数为组件,返回值为新组件的函数.

HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。开发过程中,有的功能需要在多个组件内复用时,这时可以创建一个 Hoc。

二.作用

高阶组件的作用其实就是为了是业务功能复用,当很多组件有相同的功能的时候,可以考虑使用HOC,使业务功能复用.HOC最大的好处就是解耦和灵活性.高阶组件的实现原理是装饰器模式(Decorator Pattern)!

三.高阶组件的实现

高阶组件的实现,在React中,有两种主流的方式来实现高阶组件.使用属性代理(Props Proxy)和反向继承(Inheritance Inversion),两种方法包含了几种包装WrappedComponent的方法.

1.属性代理(Props Proxy)

//高阶组件
import React from 'react';

class Hello extends React.Component {
    render() {
        return (
            <div>
                {this.props.name}
                Hello,高阶组件
            </div>
        );
    }
}

//创建一个高阶组件
//将组件作为参数传递,然后再返回一个新组件
function Hoc(WrappedComponent) {
    //作为代理,将组件抛出去,并传递props
    return class Proxy extends React.Component {
        render() {
            return (
                <WrappedComponent {...this.props}></WrappedComponent>
            );
        }
    };
}

//引用高阶组件
const wrappedHoc = Hoc(Hello);

export default wrappedHoc;

上述代码中,我定义了一个名为HOC的高阶函数,参数是WrappedComponen,然后在函数内部声明了一个组件代理,将传递进来的WrappedComponen,处理之后再给抛出去,并且还传递了Props信息

PS:高阶函数内部的组件名Proxy可以省略

1.1 通过refs获取组件实例(不推荐)

ref如果写在组件上,那么获取的是组件的实例对象

如果ref写在组件内标签上(div,input等),获取的是相应的DOM节点

//创建一个高阶组件
//将组件作为参数传递,然后再返回一个新组件
const Hoc = (WrappedComponent) => {
    //作为代理,将组件抛出去,并传递props
    return class Proxy extends React.Component {
        proc = (instance) => {
            instance.sayHello();
            console.log(instance.props.name);
            console.log(instance.state.msg);
        }
        render() {
            // ref是无法当做props传递过去的
            const props = Object.assign({}, this.props, { ref: this.proc });
            return (
                //可以通过ref关键字,来获取传递的组件的实例对象
                <WrappedComponent {...props}></WrappedComponent>
            );
        }
    };
};

在高阶组件中,可以通过refs获取被包装的原组件的实例!并且可以直接调用原组件的实例方法

PS:不能在无状态组件(函数组件)上使用ref,因为无状态组件没有实例!

1.2 抽象state
/*-----------------------------抽象state-----------------------------------*/
//创建一个高阶组件(将组件的状态抽象出来)
const HocState = (WrappedComponent) => {
    return class extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                value: ''
            };
        }

        HandleChange = (e) => {
            this.setState({
                value: e.target.value
            });
        }

        render() {
            const newProps = {
                HandleChange: this.HandleChange,
                value: this.state.value
            };
            return (
                <div>
                    <WrappedComponent {...this.props} {...newProps}></WrappedComponent>
                </div>
            );
        }
    };
};

//Input组件
class MyInput extends React.Component {
    render() {
        return (
            <div>
                <input type="text" value={this.props.value} onChange={this.props.HandleChange}/>
            </div>
        );
    }
}

//文本域组件
class MyTextArea extends React.Component {
    render() {
        return (
            <div>
                <textarea value={this.props.value} onChange={this.props.HandleChange} cols="30" rows="10" ></textarea>
            </div>
        );
    }
}

const HocInput = HocState(MyInput);
const HocTextArea = HocState(MyTextArea);

export default class Hello extends React.Component {
    render() {
        return (
            <div>
                <HocInput></HocInput>
                <HocTextArea></HocTextArea>
            </div>
        );
    }
}

上述例子,我们将input和textarea的不受控组件转变成了受控组件,他们俩拥有重复的逻辑,也就是state中的value,和onChange中的事件,这时为了达到逻辑复用的目的,我们使用高阶组件,同时抽象state,共同提到高阶组件中进行管理.

1.3 操作props

可以对基础组件的Props进行增加,修改,删除

//声明一个高阶组件
const HocProps = WrappedComponent => class extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        let {userinfo,baseinfo}=this.props.data;
        
        return (
            <div>
                {/* 
                    对包装组件的props过滤,也就是删除 
                    还可以添加props,当然也可以修改
                */}
                <WrappedComponent userinfo={userinfo} isShow={false}></WrappedComponent>
            </div>
        );
    }
};

在高阶组件中,可以对要传递到被包装的组件的props进行,删除,修改,以及添加,包括hoc中定义的自定义事件,都可以通过props再传下去。

1.4 基础组件和其他元素组合

对于布局的考虑,或者操作样式,可以将基础组件与其他组件包装在一起.

//声明一个高阶组件
const HocProps = WrappedComponent => class extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        let {userinfo,baseinfo}=this.props.data;
        
        return (
            //控制样式
            <div className={this.box}>
                {/* 
                    对包装组件的props过滤,也就是删除 
                    还可以添加props,当然也可以修改
                */}
                <WrappedComponent userinfo={userinfo} isShow={false}></WrappedComponent>
            </div>
        );
    }
};

2.反向继承

高阶组件的反向继承指的是高阶组件继承自基础组件,并不是高阶组件继承传入的基础组件. 那么呢由于高阶组件继承了基础组件,所以高阶组件可以通过this来操作基础组件的State,Props以及基础组件的方法,甚至可以通过super来操作基础组件的生命周期

//------------------------------------------------反向继承---------------------------------
const IIHoc = WrappedComponent => class extends WrappedComponent {
    constructor(props){
        super(props);
    }
    render() {
        return this.super().render();
    }
};

上述代码中,一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法。

1.操作State

反向继承的高阶组件由于继承了基础,所以可以至直接读取,编辑和删除基础组件实例中的state,更是可以添加state,

PS: 一般不建议在高阶组件中操作State,因为这样会让基础组件的state混乱,导致基础组件的内部被破坏,出现莫名其妙的错误.

class BaseWorld extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'liuqiao'
        };
    }
    render() {
        console.log(this.props);
        return (
            <div>
                {this.state.name}
            </div>
        );
    }
}

const IIHoc = WrappedComponent => class extends WrappedComponent {
    constructor(props) {
        super(props);
        this.state = {
            age: 18
        };
    }

    Click = () => {
        //改变原始组件的state
        this.setState({
            name: 'zhangsan'
        });
    }

    render() {
        return (
            <div>
                {/* 读取原组件的state */}
                {this.state.name}
                <button onClick={this.Click}>改变</button>
            </div>
        );
    }
};

const IIHocBase = IIHoc(BaseWorld);
2.渲染劫持

渲染劫持指的是高阶组件控制基础组件的渲染效果,因为基础组件的渲染被控制,高阶组件可以进行渲染劫持

  • 根据条件控制渲染效果
  • 改变dom样式树的样式
  • 改变基础组件中一下呗渲染元素的props
  • 操作dom树中的元素

四.高阶组件在项目中的运用

1.进行权限的判断

可以在高阶组件内,实现权限判断,跳转路由

2.日志记录

将日志记录的逻辑抽出来,封装一个高阶组件,需要写日志的地方进行引用

3.数据校验

需要进行数据校验的逻辑复用时,可以考虑高阶组件

4.异常处理

如果需要对所有的异常情况进行处理,可以封装处理异常的高阶组件

五.总结

  • 高阶组件是一个把某个组件装饰城另一个组件的函数
  • 高阶组件的作用是解耦,争强灵活性,进行代码复用
  • 高阶组件的核心思想是在装饰器模式

猜你喜欢

转载自blog.csdn.net/liuqiao0327/article/details/107284115