问题
起因是这样的,一开始在项目中写了一个原始的 Popup 组件(通过 react-redux 的 connect 方法接入 redux),后来有了一个更高级的需求,我就想要直接 extends 这个原始的 Popup 组件,然后覆写一些方法。 然后遇到了一个问题,继承之后的子组件没有父组件的自定义方法。
解疑
最初一度怀疑是否是 babel 的 decorator 插件有问题(毕竟不是浏览器天然支持的特性),但是仔细看了 decorator 插件的源码,发现并不存在这种问题。 后来在这个插件的 issues 里看到了这么一个 issue,问题并不算是同一个,但是作者提到,react-redux
的 connect
方法可能实现有一些问题(当然这个地方并不是),我就想到去看看 connect 的实现。 然后就看到了底层方法 connectAdvanced
中有这么一句代码:
export default connectAdvanced() {
// ...
return hoistStatics(Connect, WrappedComponent);
}
hoistStatics 是一个第三方库 hoist-non-react-statics,它干的事情也比较简单:
Copies non-react specific statics from a child component to a parent component. Similar to Object.assign, but with React static keywords blacklisted from being overridden.
简单来说,就是复制所有非 React 自有的属性,因为使用了 Object.getOwnPropertyNames()
的方法,所以我们定义在 Component 上的所有方法就不会被复制到这个新的对象上了。 而在 connectAdvanced
内部,render 时实际是这样的:
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
通过 createElement
方法给原始的 WrappedComponent
传入额外的 props,来实现 connect 的效果。 总之,最终 connect 方法返回这个对象,其实和原始的 Component 的结构并不一样,虽然它的 WrappedComponent
属性指向了原始的 Component,但是显然在我们的场景下,并不能使用。
那怎么办呢
通常的推荐解决方式就是『高阶组件』,这样就屏蔽了 connect 的逻辑。
为什么要用 hoist-non-react-statics 这种模式
关于这个问题,作者在 这一个 issue 的回答中 也说了很多,总结来说,就是为了避免使用者可以直接的接触到 connect 之后的组件的内部方法,防止因为各种改动导致组件的 "break",这也算是为了提升健壮性。