高阶组件是一个纯函数,也是容器组件.是参数为组件,返回值为新组件的函数.
HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。开发过程中,有的功能需要在多个组件内复用时,这时可以创建一个 Hoc。
高阶组件的作用其实就是为了是业务功能复用,当很多组件有相同的功能的时候,可以考虑使用HOC,使业务功能复用.HOC最大的好处就是解耦和灵活性.高阶组件的实现原理是装饰器模式(Decorator Pattern)!
高阶组件的实现,在React中,有两种主流的方式来实现高阶组件.使用属性代理(Props Proxy)和反向继承(Inheritance Inversion),两种方法包含了几种包装WrappedComponent的方法.
//高阶组件
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
可以省略
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,因为无状态组件没有实例!
/*-----------------------------抽象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,共同提到高阶组件中进行管理.
可以对基础组件的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再传下去。
对于布局的考虑,或者操作样式,可以将基础组件与其他组件包装在一起.
//声明一个高阶组件
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>
);
}
};
高阶组件的反向继承指的是高阶组件继承自基础组件,并不是高阶组件继承传入的基础组件. 那么呢由于高阶组件继承了基础组件,所以高阶组件可以通过this来操作基础组件的
State
,Props
以及基础组件的方法,甚至可以通过super来操作基础组件的生命周期
//------------------------------------------------反向继承---------------------------------
const IIHoc = WrappedComponent => class extends WrappedComponent {
constructor(props){
super(props);
}
render() {
return this.super().render();
}
};
上述代码中,一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法。
反向继承的高阶组件由于继承了基础,所以可以至直接读取,编辑和删除基础组件实例中的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);
渲染劫持指的是高阶组件控制基础组件的渲染效果,因为基础组件的渲染被控制,高阶组件可以进行渲染劫持
可以在高阶组件内,实现权限判断,跳转路由
将日志记录的逻辑抽出来,封装一个高阶组件,需要写日志的地方进行引用
需要进行数据校验的逻辑复用时,可以考虑高阶组件
如果需要对所有的异常情况进行处理,可以封装处理异常的高阶组件