深入 React 高阶组件

- Published on

深入 React 高阶组件
解决关注点横切,抽取出组件的公共部分,是大型应用的必备工作。
1. 关注点横切
目的:?组件抽象?
关注点是是什么 ❓
关注点指某个功能(如日志)。
横切关注点是指面向对象中某个关注点在多个模块中使用(如日志模块)。
2. 如何解决关注点横切
3.mixin
3.1 什么是 mixin
3.2 如何使用 mixin
3.3 mixin 使用技巧
3.4 mixin 的应用
3.5 mixin 注意事项
4. 高阶组件
4.1 高阶组件是什么?
高阶组件(HOC)
是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
可以看出,HOC 的主要作用是复用组件。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
1.2.1 高阶组件解决横切关注点问题
如果我们需要在不同的模块中做以下事情:
- 在挂载时,向 DataSource 添加一个更改侦听器(用于获取数据)
- 在侦听器内部,当数据源发生变化时,调用 setState(设置数据)
- 在卸载时,删除侦听器(删除监听器)
CommentList 和 BlogPost 两个组件都需要获取评论数据:
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);
第一个参数是被包装组件。
第二个参数通过 DataSource 和当前的 props 返回我们需要的数据。
高价组件示例:
// 此函数接收一个组件(传入组件)...
function withSubscription(WrappedComponent, selectData) {
// ...并返回另一个组件(增强组件)...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props),
};
}
componentDidMount() {
// ...负责订阅相关的操作...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props),
});
}
render() {
// ... 并使用新数据渲染被包装的组件!
// 请注意,我们可能还会传递其他属性
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
HOC 特性:
- HOC 接收一个组件(传入组件),使用数据将该组件组合成一个新组件,并返回新组件(增强组件)
- HOC 不会修改传入的组件,只是添加了某些属性
- HOC 应该是纯函数,不能有副作用
HOC 错误示例:❌
HOC 中修改组件原型:
function logProps(InputComponent) {
InputComponent.prototype.componentDidUpdate = function (prevProps) {
console.log("Current props: ", this.props);
console.log("Previous props: ", prevProps);
};
// 返回原始的 input 组件,暗示它已经被修改。
return InputComponent;
}
// 每次调用 logProps 时,生命周期函数componentDidUpdate都会执行,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent);
2. 高阶组件有何用?
定义中已经说了,高阶组件是为了解决 React 组件的组合问题。
高阶组件主要解决关注点横切的问题。
什么是关注点横切❓
通俗的说就是,某些任务(关注点)被多个模块重复使用(横切)。
常见的关注点横切有:
- 日志功能:多个模块可能都会用到
- 获取用户信息
一句话终结就是:HOC 可以帮助我们抽象出组件的公共部分。
3. 高阶组件和普通组件的区别
高阶组件和普通组件的区别是:
普通组件是将 props/state 转换为 UI,而高阶组件是将组件转换为另一个组件。
上面这句话来自React-高阶组件官方文档,它具有纲领意义。
如果我们需要处理东西和 UI 相关,那么,他就是组件的组合问题。
如果我们处理的东西是多个组件共同所有的,想要把共有的处理办法提取出来,那么就需要使用 HOC。
4. 如何实现高阶组件
高阶组件就是要操作 WrappedComponent,得到一个增强的新组件。
如何操作呢 ❓
- 属性代理(props proxy):高阶组件通过被包裹的 React 组件来操作 props
- 反向继承(inheritance inversion):高阶组件继承于被包裹的组件
3.1 属性代理
Props Proxy 的简单实现:
function HOC(WrappedComponent) {
return class EnhancedComponent extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
需要说明的是,EnhancedComponent 在这里是容器组件
,它包裹着包裹组件(WrappedComponent),🌟 就是它来处理具体增强逻辑。
HOC 做了什么?
通过给 WrappedComponent 传递 this.props 得到一个新的组件。
this.props
是WrappedComponent本身具有的属性,使用EnhancedComponent时可以直接使用它们。
示例 1:
import { Component } from "react";
// 定义被包裹组件
const Mouse = (props) => {
return (
<span className="mouse" role={props.role}>
🐭
</span>
);
};
// 定义 HOC
const withDrag = (Wrapped) => {
class WithDrag extends Component {
render = () => {
// this.props是要透传给 Wrapped 的属性
return <Wrapped {...this.props} />;
};
}
return WithDrag;
};
// 生成增强组件 MouseDraggable
const MouseDraggable = withDrag(Mouse);
// 使用增强组件
ReactDOM.render(
<MouseDraggable role="mouse" />,
document.getElementById("root")
);
这是个基础示例,并没有增强Mouse 组件的任何功能。
我们一起看看属性代理可以做什么:
- 操作 props
- 通过 Refs 访问到组件实例
- 提取 state
- 用其他元素包裹 WrappedComponent
3.1.1 操作 props
可以读取、添加、编辑、删除传给 WrappedComponent 的 props。
改进例 1 中的 HOC:
const withDrag = (Wrapped) => {
class WithDrag extends Component {
// 增加新的 props
const newProps = {
title: "draggable"
}
render = () => {
return <Wrapped {...this.props} {...newProps} />;
};
}
return WithDrag;
};
3.1.2 通过 Refs 访问到组件实例
ref
像key
一样,它不是 prop 属性,会被 React 进行特殊处理。
如果对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。
...
const withDrag = (Wrapped) => {
class WithDrag extends Component {
render = () => {
return <Wrapped {...this.props} />;
};
}
return WithDrag;
};
const MouseDraggable = withDrag(Mouse);
// 创建ref
const ref = React.createRef();
ReactDOM.render(
<MouseDraggable role="mouse" ref={ref} />,
document.getElementById("root")
);
这里的 ref 指向 HOC 的容器组件 -- WithDrag。
React.forwardRef
就是一个高阶组件,它能帮我们转发 ref 到增强后的组件。
import { Component } from "react";
const Mouse = (props) => {
return (
<span className="mouse" role={props.role}>
🐭
</span>
);
};
const withDrag = (Wrapped) => {
class WithDrag extends Component {
render = () => {
return <Wrapped {...this.props} />;
};
}
return WithDrag;
};
const MouseDraggable = withDrag(Mouse);
const ref = React.createRef();
ReactDOM.render(
<MouseDraggable role="mouse" ref={ref} />,
document.getElementById("root")
);
这里的 ref 指向 MouseDraggable。
3.2 反向继承
4. 约定
既然高阶组件是一种设计模式,我们应该准守它的设计约定。
5. 注意事项
参考
Blog:
浅显易懂,动画辅助 -- REACT HIGHER ORDER COMPONENTS IN 3 MINUTES
深入讲解,值得研究 -- React Higher Order Components in depth
文档:
参考
官方文档,总结全面,但不太容易理解 -- 高阶组件