HOC简介
HOC全称为High Order Component即高阶组件, 其使用姿势通常有两种:
属性代理(Props Proxy)
- 操作props
- 抽象state
- 获取ref
- 用其他组件包裹(功能集成/样式包裹)
简而言之: 修改传入组件的props.
使用姿势常常是这样的:
const ppHoc = WrappedComponent => class extends React.Component {
render() {
// 此处可以修改props,或者注入state,
// 总之对WrappedComponent而言就是修改了props
return <WrappedComponent {...this.props} />
}
}
反向继承(Inheritance Inversion)
- 渲染劫持 render hijacking
- 操纵state
使用姿势常常是这样的:
const iiHoc = WrappedComponent => class extends WrappedComponent{
render() {
const elementTree = super.render();
const { props } = elementTree;
// 可以修改props
const newProps = {aa: 1};
return React.cloneElement(elementTree, {...props, ...newProps}, elementsTree.props.children)
}
}
继承该组件,可基于其elementTree进行修改。能力更强,但风险也更高。
不能保证完整的子组件树被解析, 以及静态方法不会被继承。
实践
需求简介:
目前页面中已有多个图表组件(单一维度的数据统计折线图),目前想为每个图表添加checkBox,可以交叉其他维度进行统计。
需求分析:
目前业务中每个图表是一个封装好的组件,(如图一所示,标题一行和图表是一体的,为包装好的Chart组件)。现在业务中要为每个图表都加一个CheckBox。即,需要将每个图表组件进行再次包装,将check state与chart组合成一个Component.
如果checkbox位置如图2所示,则checkBox可以作为图表组件的children,也可以作为兄弟组件,只要调整下其位置即可。
倘若checkBox要如图3,放在title和图表中间,则需要将CheckBox作为Chart的children才能插入到该位置,否则是没有空间放checkbox。如何才能以较低成本,给十来个Chart组件都添加CheckBox这个Children呢?此时就只能通过Hoc修改其props.children来实现了
按照上图所示布局,我们通过两种HOC方式都来实践下:
实践1: 属性代理
组件结构如下,state保存在Parent中,CheckBox和Chart是兄弟组件。当isChecked切换状态时,修改Chart对应的props.
Parent
CheckBox
Chart
主要代码如下
const interHoc = WrappedComponnet =>
class extends React.Component {
state = {
isChecked: false,
};
render() {
const { isChecked } = this.state;
let chartProps = { ...this.props };
// 修改props
const {
formatParams: { dims = [] },
} = chartProps;
const GENDER_TYPE = 'predicted_gender';
if (isChecked && !dims.includes(GENDER_TYPE)) {
chartProps.formatParams.dims = [GENDER_TYPE].concat(dims);
} else {
chartProps.formatParams.dims = dims.filter(d => d !== GENDER_TYPE);
}
return (
<div>
<CheckBox
checked={isChecked}
onChange={e => this.setState({ isChecked: e.target.value })}
>
交叉性别维度
</CheckBox>
<WrappedComponnet {...chartProps} />
</div>
);
}
};
此处是通过包裹另外组件实现的,也可以直接修改props.chilren = YourComponent实现。
实践2:渲染劫持
通过继承WrappedComponent,获取其elementTree, 根据原props中的参数,符合条件的,对其props和props.children进行修改。
通过继承可以直接修改elementTree(修改其props和children)显然能力范围是更强大的,但风险也更高,能不用就不用吧。
const interHoc = WrappedComponent =>
class extends WrappedComponent {
state = {
isChecked: false,
};
render() {
const { isChecked } = this.state;
const elementTree = super.render();
const interCom = (
<Checkbox
checked={isChecked}
onChange={e => this.setState({ isChecked: e.target.checked })}
>
交叉性别维度
</Checkbox>
);
// 修改props
const {
props: {
formatParams: { dims = [] },
},
} = elementTree;
const GENDER_TYPE = 'predicted_gender';
elementTree.props.children = interCom;
if (isChecked && !dims.includes(GENDER_TYPE)) {
elementTree.props.formatParams.dims = [GENDER_TYPE].concat(dims);
} else {
elementTree.props.formatParams.dims = dims.filter(
i => i !== GENDER_TYPE,
);
}
return elementTree;
}
};