基础-高阶组件
高阶函数
将其他函数作为参数或将函数作为返回值,将其称为高阶函数。
高阶组件(high-order component)
类似于高阶函数,接收 React 组件作为输入,输出一个新的 React 组件。
高阶组件让代码更具有复用性、逻辑性与抽象特征。可以对 render 方法作劫持,也可以控制 props 与 state。
高阶组件其实是一个函数,接收一个组件作为参数,返回一个包装组件作为返回值,类似于高阶函数。高阶组件和装饰器就是一个模式,因此,高阶组件可以作为 装饰器来使用。
高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。
作用
属性代理:可以将 HOC 接收到的 props 转换为另一种形式,或者将额外的 props 传递给被包装组件。
渲染劫持:可以通过修改组件的渲染逻辑来实现一些特定的需求,例如:渲染条件判断、错误处理等。
行为注入:可以在组件中注入一些行为,例如:订阅/取消订阅事件、获取/释放数据等。
例子
withLogging 是一个 HOC 函数,它接收一个组件作为参数,并返回一个新的组件。新的组件中,我们在 componentDidMount 和 componentWillUnmount 方法中打印日志,用来跟踪组件的生命周期。然后,我们将原组件通过 {...this.props} 的方式传递给新的组件,使其可以正常渲染。
import React from 'react';
function withLogging(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log(`Component ${WrappedComponent.name} mounted`);
}
componentWillUnmount() {
console.log(`Component ${WrappedComponent.name} will unmount`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
function MyComponent(props) {
return <div>{props.text}</div>;
}
export default withLogging(MyComponent);
需要注意的是,由于 HOC 返回的是一个新组件,因此它会覆盖原组件的 displayName 属性。如果需要在开发工具中正确地显示组件名称,可以在 HOC 中手动设置 displayName 属性,例如:
function withLogging(WrappedComponent) {
class WithLogging extends React.Component {
// ...
}
WithLogging.displayName = `withLogging(${WrappedComponent.displayName || WrappedComponent.name})`;
return WithLogging;
}
使用:
import React from 'react';
import withLogging from './withLogging';
function App() {
return <div><MyComponent text="Hello, world!" /></div>;
}
export default withLogging(App);
通过这种方式,我们就可以在 App 组件中使用被包装后的 MyComponent 组件,并且在组件的生命周期中打印日志,以便于调试和跟踪组件的状态变化。
需要注意的是,使用 HOC 包装组件时,要注意 props 的传递问题。由于 HOC 会返回一个新的组件,因此需要将原组件的 props 传递给新组件,否则新组件将无法正常工作。在上面的示例中,我们使用 {...this.props} 将原组件的 props 传递给了新组件,这样就可以保证原组件的 props 能够正常地传递给包装后的组件。
高阶组件的实现方法-属性代理
关于反向继承,比较复杂,实际上,因为现在类组件用的并不多,反向继承也就用的不多了。 一般的,我们通过属性代理的方式就可以解决大部分问题。
属性组件通过被包裹的 React 组件来操作 props。
属性代理组件继承自React.Component,通过传递给被包装的组件props得名
// 属性代理,把高阶组件接收到的属性传递给传进来的组件
function HOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
属性代理的用途
- 把 WrappedComponent 与其它 elements 包装在一起
- 更改 props,可以对传递的包裹组件的WrappedComponent的props进行控制
- 通过 refs 获取组件实例
1.属性代理实现高阶函数
在 render 方法中返回传入 WrappedComponent 的 React 组件。这样就可以通过高阶组件来传递 props,这种方法即为属性代理。
const MyContainer = (WrappedComponent) => {
return class extends Component {
render() {
return (
<WrappedComponent
{...props}
/>
)
}
}
}
export default MyContainer;
原始组件想要具备高阶组件对它的修饰,有两种方式。
方式一:
export default MyContainer;
class MyComponent extends Component {
}
export default MyContainer(MyComponent);
方式二:
// ES7 添加了 decorator 的属性,我们可以通过 decorator 来转换,以此简化高阶组件的调用。
@MyContainer
class MyComponent extends Component {
}
export default MyComponent;
功能上,高阶组件可以控制 props、通过 refs 使用引用、抽象 state 和使用其他元素包裹
属性代理有如下4点常见作用:
- 操作props
- 通过refs访问组件实例
- 提取state
- 用其他元素包裹WrappedComponent,实现布局等目的
1.控制 props
我们可以读取、增加、编辑或是移除从 WrappedComponent 传进来的 props,需要考虑到不能破坏原组件。应该尽量对高阶组件的 props 作新的命名以防止混淆。
// 当调用该高阶组件时,就可以使用 text 这个新的 props了。
const MyContainer = (WrappedComponent) => {
return class extends Component {
render() {
const newProps = {
text: newText
};
return (
<WrappedComponent
{...props}
{...newProps}
/>
)
}
}
}
export default MyContainer;
2.通过 refs 使用引用
在高阶组件中,可以接受 refs 使用 WrappedComponent 的引用。 可以通过ref回调函数的形式来访问传入组件的实例,进而调用组件相关方法或其他操作。
/*
当 WrappedComponent 被渲染时,refs 回调函数就会被执行,这样就会拿到一份 WrappedComponent 实例的引用。这就可以方便地用于读取或增加实例的 props,并调用实例的方法。
*/
const MyContainer = (WrappedComponent) => {
return class extends Component {
ref = (view) => {
view.mentod();
}
render() {
const props = Object.assign({}, this.props, {
ref: this.ref
});
return (
<WrappedComponent
{...props}
/>
)
}
}
}
export default MyContainer;
3. 调用高阶组件时需要传入一些参数,可以用简单方式实现。
调用高阶组件时需要传入一些参数,可以用简单方式实现。
function HOCFactoryFactory(...params) {
return function HOCFactory(WrappedComponent) {
return class HOC extends Component {
render() {
return (
<WrappedComponent
{...this.props}
/>
);
}
}
}
}
使用:
HOCFactoryFactory(params)(WrappedComponent)
或者
@HOCFactoryFactory(params)
class WrappedComponent extends Component {
}
4.包裹WrappedComponent
为了封装样式、布局等目的,可以将WrappedComponent用组件或元素包裹起来。 例如:
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
反向继承
高阶组件继承于被包裹的 React 组件。
反向继承是继承自传递过来的组件 反向继承允许高阶组件通过 this 关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期(component lifecycle)钩子,以及渲染方法(render),所以我们主要用它来做渲染劫持,比如在渲染方法中读取或更改 React Elements tree,或者有条件的渲染等。