# 从 mixin 到 hoc 再到 hook-2
# 高阶组件(HOC)
高阶组件可以看作React
对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
高阶组件(
HOC
)是React
中的高级技术,用来重用组件逻辑。但高阶组件本身并不是React API
。它只是一种模式,这种模式是由React
自身的组合性质必然产生的。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props;
if (visible === false) return null;
return <WrappedComponent {...props} />;
}
};
}
上面的代码就是一个HOC
的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个visible props
,根据visible
的值来判断是否渲染 Visible。
下面我们从以下几方面来具体探索HOC
。
# HOC 的实现方式
# 属性代理
函数返回一个我们自己定义的组件,然后在render
中返回要包裹的组件,这样我们就可以代理所有传入的props
,并且决定如何渲染,实际上 ,这种方式生成的高阶组件就是原组件的父组件,上面的函数visible
就是一个HOC
属性代理的实现方式。
function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
对比原生组件增强的项:
- 可操作所有传入的
props
- 可操作组件的生命周期
- 可操作组件的
static
方法 - 获取
refs
# 反向继承
返回一个组件,继承原组件,在render
中调用原组件的render
。由于继承了原组件,能通过 this 访问到原组件的生命周期、props、state、render
等,相比属性代理它能操作更多的属性。
function inheritHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
};
}
对比原生组件增强的项:
- 可操作所有传入的
props
- 可操作组件的生命周期
- 可操作组件的
static
方法 - 获取
refs
- 可操作
state
- 可以渲染劫持
# HOC 可以实现什么功能
# 组合渲染
可使用任何其他组件和原组件进行组合渲染,达到样式、布局复用等效果。
通过属性代理实现
function stylHOC(WrappedComponent) {
return class extends Component {
render() {
return (
<div>
<div className="title">{this.props.title}</div>
<WrappedComponent {...this.props} />
</div>
);
}
};
}
通过反向继承实现
function styleHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return (
<div>
<div className="title">{this.props.title}</div>
{super.render()}
</div>
);
}
};
}
# 条件渲染
根据特定的属性决定原组件是否渲染
通过属性代理实现
function visibleHOC(WrappedComponent) {
return class extends Component {
render() {
if (this.props.visible === false) return null;
return <WrappedComponent {...props} />;
}
};
}
通过反向继承实现
function visibleHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
if (this.props.visible === false) {
return null;
} else {
return super.render();
}
}
};
}
# 操作 props
可以对传入组件的props
进行增加、修改、删除或者根据特定的props
进行特殊的操作。
通过属性代理实现
function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
const newProps = {
...this.props,
user: "ConardLi",
};
return <WrappedComponent {...newProps} />;
}
};
}
# 获取 refs
高阶组件中可获取原组件的ref
,通过ref
获取组件实力,如下面的代码,当程序初始化完成后调用原组件的 log 方法。(不知道 refs 怎么用,请 👇Refs & DOM (opens new window))
通过属性代理实现
function refHOC(WrappedComponent) {
return class extends Component {
componentDidMount() {
this.wapperRef.log();
}
render() {
return (
<WrappedComponent
{...this.props}
ref={(ref) => {
this.wapperRef = ref;
}}
/>
);
}
};
}
这里注意:调用高阶组件的时候并不能获取到原组件的真实ref
,需要手动进行传递,具体请看传递 refs (opens new window)
# 状态管理
将原组件的状态提取到HOC
中进行管理,如下面的代码,我们将Input
的value
提取到HOC
中进行管理,使它变成受控组件,同时不影响它使用onChange
方法进行一些其他操作。基于这种方式,我们可以实现一个简单的双向绑定
,具体请看双向绑定 (opens new window)。
通过属性代理实现
function proxyHoc(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = { value: "" };
}
onChange = (event) => {
const { onChange } = this.props;
this.setState(
{
value: event.target.value,
},
() => {
if (typeof onChange === "function") {
onChange(event);
}
}
);
};
render() {
const newProps = {
value: this.state.value,
onChange: this.onChange,
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
class HOC extends Component {
render() {
return <input {...this.props}></input>;
}
}
export default proxyHoc(HOC);
# 操作 state
上面的例子通过属性代理利用 HOC 的 state 对原组件进行了一定的增强,但并不能直接控制原组件的state
,而通过反向继承,我们可以直接操作原组件的state
。但是并不推荐直接修改或添加原组件的state
,因为这样有可能和组件内部的操作构成冲突。
通过反向继承实现
function debugHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
console.log("props", this.props);
console.log("state", this.state);
return <div className="debuging">{super.render()}</div>;
}
};
}
上面的HOC
在render
中将props
和state
打印出来,可以用作调试阶段,当然你可以在里面写更多的调试代码。想象一下,只需要在我们想要调试的组件上加上@debug
就可以对该组件进行调试,而不需要在每次调试的时候写很多冗余代码。(如果你还不知道怎么使用 HOC,请 👇如何使用 HOC (opens new window))
# 渲染劫持
高阶组件可以在 render 函数中做非常多的操作,从而控制原组件的渲染输出。只要改变了原组件的渲染,我们都将它称之为一种渲染劫持
。
实际上,上面的组合渲染 (opens new window)和条件渲染 (opens new window)都是渲染劫持
的一种,通过反向继承,不仅可以实现以上两点,还可直接增强
由原组件render
函数产生的React元素
。
通过反向继承实现
function hijackHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
const tree = super.render();
let newProps = {};
if (tree && tree.type === "input") {
newProps = { value: "渲染被劫持了" };
}
const props = Object.assign({}, tree.props, newProps);
const newTree = React.cloneElement(tree, props, tree.props.children);
return newTree;
}
};
}
注意上面的说明我用的是增强
而不是更改
。render
函数内实际上是调用React.creatElement
产生的React元素
:
虽然我们能拿到它,但是我们不能直接修改它里面的属性,我们通过getOwnPropertyDescriptors
函数来打印下它的配置项:
可以发现,所有的writable
属性均被配置为了false
,即所有属性是不可变的。(对这些配置项有疑问,请 👇defineProperty (opens new window))
不能直接修改,我们可以借助cloneElement
方法来在原组件的基础上增强一个新组件:
React.cloneElement()
克隆并返回一个新的React元素
,使用element
作为起点。生成的元素将会拥有原始元素 props 与新 props 的浅合并。新的子级会替换现有的子级。来自原始元素的 key 和 ref 将会保留。
React.cloneElement()
几乎相当于:
<element.type {...element.props} {...props}>
{children}
</element.type>
# 如何使用 HOC
上面的示例代码都写的是如何声明一个HOC
,HOC
实际上是一个函数,所以我们将要增强的组件作为参数调用HOC
函数,得到增强后的组件。
class myComponent extends Component {
render() {
return <span>原组件</span>;
}
}
export default inheritHOC(myComponent);
# compose
在实际应用中,一个组件可能被多个HOC
增强,我们使用的是被所有的HOC
增强后的组件,借用一张装饰模式
的图来说明,可能更容易理解:
假设现在我们有logger
,visible
,style
等多个HOC
,现在要同时增强一个Input
组件:
logger(visible(style(Input)));
这种代码非常的难以阅读,我们可以手动封装一个简单的函数组合工具,将写法改写如下:
const compose = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
compose(logger, visible, style)(Input);
compose
函数返回一个所有函数组合后的函数,compose(f, g, h)
和 (...args) => f(g(h(...args)))
是一样的。
很多第三方库都提供了类似compose
的函数,例如lodash.flowRight
,Redux
提供的combineReducers
函数等。
# Decorators
我们还可以借助ES7
为我们提供的Decorators
来让我们的写法变的更加优雅:
@logger
@visible
@style
class Input extends Component {
// ...
}
Decorators
是ES7
的一个提案,还没有被标准化,但目前Babel
转码器已经支持,我们需要提前配置babel-plugin-transform-decorators-legacy
:
"plugins": ["transform-decorators-legacy"]
还可以结合上面的compose
函数使用:
const hoc = compose(logger, visible, style);
@hoc
class Input extends Component {
// ...
}
文中如有错误,欢迎在评论区指正,谢谢阅读。
Last Updated: 8/4/2019, 10:35:29 AM
← 从 Mixin 到 HOC 再到 Hook(一) (opens new window)从 Mixin 到 HOC 再到 Hook(三) (opens new window)→