高阶组件
HOC 是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 组合特性而形成的设计模式
参数为组件,返回值为新函数
js
const EnhanceComponent = higherOrderComponent(WrappedComponent);
组件将 props 转换为 UI,高阶组件将组件转换为另一个组件
如: Redux 的 connect
属性代理
js
function HOC(WrapCompnent) {
return class Advance extends React.Component {
state = {
name: "benben",
};
render() {
return <WrapCompnent {...this.props} {...this.state} />;
}
};
}
优点:
- 属性代理可以和业务组件低耦合,零耦合,对于条件渲染和 props 属性增强,只负责控制子组件渲染和传递额外的 props
所以无需知道业务组件做了什么 - 同样适用于类组件和函数组件
- 可以完全隔离业务组件的渲染,因为属性代理可以理解为一个新组件,可以控制业务组件是否渲染
- 可以嵌套使用,多个 HOC 可以嵌套使用,而且一般不会限制包装 HOC 的先后顺序
缺点:
- 一般无法直接获取原始组件的状态,如果想要获取,需要通过 ref 获取组件实例
- 无法直接集成静态属性。如果需要继承则要手动处理,或者引入第三方库
- 本质上是产生了一个新组件,所以需要配合 forwardRef 来转发 ref
反向继承
- 包装后的组件继承了原始组件本身,所以此时无须再去挂载业务组件
js
class Index extends React.Component {
render() {
return <div>hello world</div>
}
function HOC(Component) {
return class wrapComponent extends Component {
}
}
}
export default HOC(Index)
优点:
- 方便获取组件内部状态,如 state/props/生命周期/绑定的函数等
- es6 继承可以良好地继承静态属性。无须对静态属性方法做额外的处理
缺点:
- 函数组件无法使用
- 和被包装的组件耦合度高,需要知道被包装的原始组件的内部状态
- 如果多个反向继承 HOC 嵌套在一起,当前状态会覆盖上一个状态,比如说 componentDidMount 这样的副作用串联起来
渲染劫持
js
class HOC = (WrapComponent) => {
class Index extends WrapComponent {
render() {
if(this.props.visible) {
return super.render()
} else {
return <div>no content</div>
}
}
}
}
js
class Index extends React.Component {
render() {
return (
<div>
<ul>
<li>react</li>
<li>vue</li>
<li>Angular</li>
</ul>
</div>
);
}
}
function HOC(Component) {
return class Advance extends Component {
render() {
const element = super.render();
const otherProps = {
name: "alien",
};
/* 替换 Angular 元素节点 */
const appendElement = React.createElement(
"li",
{},
`hello ,world , my name is ${otherProps.name}`
);
const newchild = React.Children.map(
element.props.children.props.children,
(child, index) => {
if (index === 2) return appendElement;
return child;
}
);
return React.cloneElement(element, element.props, newchild);
}
};
}
export default HOC(Index);
下面对 HOC 具体能实现那些功能,和如何编写做一下总结:
- 强化 props ,可以通过 HOC ,向原始组件混入一些状态。
- 渲染劫持,可以利用 HOC ,动态挂载原始组件,还可以先获取原始组件的渲染树,进行可控性修改。
- 可以配合 import 等 api ,实现动态加载组件,实现代码分割,加入 loading 效果。
- 可以通过 ref 来获取原始组件实例,操作实例下的属性和方法。
- 可以对原始组件做一些事件监听,错误监控等。
手写HOC
js
function withRouter(Component) {
const displayName = `withRouter (${Component.displayName || Component.name})`;
const C = (props) => {
const { wrapperComponentRef, ...remainingProps } = props;
return (
<RouterContext.Consumer>
{(context) => {
return (
<Component
{...remainingProps}
{...context}
ref={wrapperComponentRef}
/>
);
}}
</RouterContext.Consumer>
);
};
C.displayName = displayName;
C.wrapperComponent = Component;
return hoistStatics(C, Component);
}