文章目录
什么是高阶组件
高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的React组件,供其他组件调用。它只是处理统一包装(处理)其它组件的一种模式。
高阶组件不是组件,而是一个函数,参数为组件,返回值为新组价
const EnhancedComponent = higherOrderComponent(WrappedComponent);
function HOCFactory(WrappedComponent) {
return class HOC extends React.Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
如何实现高阶组件
通常情况下有两种方式
- 属性代理(Props Proxy)
- 反向继承(Inheritance Inversion)
高阶组件可以做什么
- 代码复用、代码模块化
- 增删改props
- 渲染劫持
增删改props
可以通过对传入的props进行修改,或者添加新的props来达到增删改props的效果。
比如你想要给wrappedComponent增加一个props,可以这么搞:
function control(wrappedComponent) {
return class Control extends React.Component {
render(){
let props = {
...this.props,
message: "You are under control"
};
return <wrappedComponent {...props} />
}
}
}
这样,你就可以在你的组件中使用message这个props:
class MyComponent extends React.Component {
render(){
return <div>{this.props.message}</div>
}
}
export default control(MyComponent);
渲染劫持
这里的渲染劫持并不是你能控制它渲染的细节,而是控制是否去渲染。由于细节属于组件内部的render方法控制,所以你无法控制渲染细节。
比如,组件要在data没有加载完的时候,现实loading…,就可以这么写:
function loading(wrappedComponent) {
return class Loading extends React.Component {
render(){
if(!this.props.data) {
return <div>loading...</div>
}
return <wrappedComponent {...props} />
}
}
}
这个样子,在父级没有传入data的时候,这一块儿就只会显示loading…,不会显示组件的具体内容
class MyComponent extends React.Component {
render(){
return <div>{this.props.data}</div>
}
}
export default control(MyComponent);
使用HOC需要注意什么
尽量不要随意修改下级组件需要的props
之所以这么说,是因为修改父级传给下级的props是有一定风险的,可能会造成下级组件发生错误。比如,原本需要一个name的props,但是在HOC中给删掉了,那么下级组件或许就无法正常渲染,甚至报错。
Ref无法获取你想要的ref
以前你在父组件中使用的时候,你可以直接通过this.refs.component进行获取。但是因为这里的component经过HOC的封装,已经是HOC里面的那个component了,所以你无法获取你想要的那个ref(wrappedComponent的ref)。
这个问题的解决方案是通过使用 ref转发(React 16.3 中引入)
不要在 render 方法中使用 HOC
React 的 diff 算法(称为协调)使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树。 如果从 render 返回的组件与前一个渲染中的组件相同(===),则 React 通过将子树与新子树进行区分来递归更新子树。 如果它们不相等,则完全卸载前一个子树。
通常,你不需要考虑这点。但对 HOC 来说这一点很重要,因为这代表着你不应在组件的 render 方法中对一个组件应用 HOC:
render() {
// 每次调用 render 函数都会创建一个新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
return <EnhancedComponent />;
}
这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失。
如果在组件之外创建 HOC,这样一来组件只会创建一次。因此,每次 render 时都会是同一个组件。一般来说,这跟你的预期表现是一致的。
在极少数情况下,你需要动态调用 HOC。你可以在组件的生命周期方法或其构造函数中进行调用
务必复制静态方法
有时在 React 组件上定义静态方法很有用。例如,Relay 容器暴露了一个静态方法 getFragment 以方便组合 GraphQL 片段。
但是,当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。
// 定义静态函数
WrappedComponent.staticMethod = function() {/*...*/}
// 现在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 必须准确知道应该拷贝哪些方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
但要这样做,你需要知道哪些方法应该被拷贝。你可以使用 hoist-non-react-statics自动拷贝所有非 React 静态方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
参考
https://blog.youkuaiyun.com/qq_40479190/article/details/78324244
http://huziketang.mangojuice.top/books/react/lesson28
https://juejin.im/post/5914fb4a0ce4630069d1f3f6
https://blog.youkuaiyun.com/wjk_along/article/details/83479686
https://www.jianshu.com/p/0aae7d4d9bc1
https://segmentfault.com/a/1190000008112017