React高阶组件HOC
😊文章略长,做好心里准备哦,建议阅读15分钟。
定义
- HOC其实是一个函数,接收一个组件作为参数,返回一个包装组件作为返回值
- 在多个不同的组件中需要用到相同的功能
- 高阶组件和装饰器就是一个模式,因此高阶组件可以作为装饰器来使用
作用
- 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。
- Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。
补充说明装饰器
在下面很多案例中用到了装饰器(@),这里提前说明下,修饰器(Decorator)是一个函数,用来修改类的行为。
更多用法可参考简书一篇文章:www.jianshu.com/p/275bf41f4…
基本形式
const EnhancedComponent = higherOrderComponent(WrappedComponent)
function hoc(WrappedComponent) {
return class HOC extends React.Component {
componentDidMount() {
console.log("hoc");
}
render() {
return <WrappedComponent />
}
}
}
// 使用高阶组件
class ComponentClass extends React.Component {
render() {
return <div></div>
}
}
export default hoc(ComponentClass);
// 作为装饰器使用
@hoc
export default class ComponentClass extends React.Component {
//...
}
常见用法
- 属性代理(Props Proxy): 高阶组件通过ComponentClass的props来进行相关操作
- 继承反转(Inheritance Inversion)):高阶组件继承自ComponentClass
属性代理(Props Proxy)常见作用
- 操作props,可以对原组件的props进行增删改查,需要考虑到不能破坏原组件
// 添加新的props
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
- 通过refs访问组件实例,进而调用组件相关方法
// WrappedComponent初始渲染时候会调用ref回调,传入组件实例,在proc方法中,就可以调用组件方法
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
// Object.asign();将所有可枚举属性的值从一个或多个源对象复制到目标对象
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return <WrappedComponent {...props}/>
}
}
}
- 提取state,可以通过传入 props 和回调函数把 state 提取出来
// 提取了 input 的 value 和 onChange 方法
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
state = {
name: ''
}
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange.bind(this)
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
// 使用方式如下
@ppHOC
class Example extends React.Component {
render() {
// 使用ppHOC装饰器之后,组件的props被添加了name属性,input会成为受控组件
return <input name="name" {...this.props.name}/>
}
}
- 用其他元素包裹WrappedComponent,实现布局,样式等目的
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
继承反转(Inheritance Inversion)常见作用
- 渲染劫持,HOC 控制着 WrappedComponent 的渲染输出,可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容
// 过滤掉原组件中的ul元素
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
const elementTree = super.render();
elementTree.props.children = elementTree.props.children.filter((z) => z.type !== "ul")
return React.cloneElement(elementTree);
}
}
}
@hoc
export default class ComponentClass extends React.Component {
render() {
return (
<div>
<p style={{color: 'brown'}}>啦啦啦</p>
<ul>
<li>1</li>
<li>2</li>
</ul>
</div>
)
}
}
- 操作state,HOC可以操作WrappedComponent实例的state。但这样会破坏WrappedComponent的state,所以要限制HOC读取或添加state,添加的state应该放在单独的命名空间里
export function IIHOC(WrappedComponent) {
return class II extends WrappedComponent {
render() {
return (
<div>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{
super.render()
}
</div>
)
}
}
}
- 条件渲染
// 假设this.props.loggedIn为真,HOC会完全渲染WrappedComponent 的渲染结果
function iiHOC(WrappedComponent) {
return class ii extends WrappedComponent {
render() {
if (this.props.loggedIn) {
return super.render()
} else {
return null
}
}
}
}
- 解决WrappedComponent名字丢失问题
// 用HOC包裹的组件会丢失原先的名字,影响开发和调试,可以在WrappedComponent的名字加前缀来作为HOC的名字
const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
static displayName = `withModal(${componentName})`;
实际应用
- 记录localStorage返回值
//通过多重高阶组件确定key并设定组件
const withStorage = (key) => WrappedComponent => {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem(key);
this.setState({data});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
@withStorage('data')
class MyComponent2 extends Component {
render() {
return <div>{this.props.data}</div>
}
}
@withStorage('name')
class MyComponent3 extends Component {
render() {
return <div>{this.props.data}</div>
}
}
- 项目中,每次重新打开modal框,每次销毁modal中数据,防止数据污染
const modalHoc = (options) => WrappedComponent => {
const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
return class ModalComponent extends Component {
static displayName = `withModal(${componentName})`;
render() {
const {visible, onCancel} = this.props;
let title;
if (typeof options === 'string') title = options;
if (typeof options === 'function') title = options;
if (typeof options === 'object') title = options.title;
if (typeof title === 'function') title = title(this.props);
return (
<Modal
destroyOnClose
width="60%"
bodyStyle={{padding: 0}}
footer={null}
{...options}
title={title}
onCancel={onCancel}
visible={visible}
>
<WrappedComponent {...this.props}/>
</Modal>
);
}
}
};
@modalHoc('可以传入不同类型标题')
😊 参考链接:react.html.cn/docs/higher…
😊 刚刚加入掘金社区,欢迎提出宝贵建议,一起进步学习!