React组件复用的方式 (3)

由于实际上是继承关系,我们可以去读取组件的props和state,如果有必要的话,甚至可以修改增加、修改和删除props和state,当然前提是修改带来的风险需要你自己来控制。在一些情况下,我们可能需要为高阶属性传入一些参数,那我们就可以通过柯里化的形式传入参数,配合高阶组件可以完成对组件的类似于闭包的操作。

const HOCFactoryFactory = (params) => { // 此处操作params return (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { render() { return params.isRender && this.props.isRender && super.render(); } } } } 注意 不要改变原始组件

不要试图在HOC中修改组件原型,或以其他方式改变它。

function logProps(InputComponent) { InputComponent.prototype.componentDidUpdate = function(prevProps) { console.log("Current props: ", this.props); console.log("Previous props: ", prevProps); }; // 返回原始的 input 组件,其已经被修改。 return InputComponent; } // 每次调用 logProps 时,增强组件都会有 log 输出。 const EnhancedComponent = logProps(InputComponent);

这样做会产生一些不良后果,其一是输入组件再也无法像HOC增强之前那样使用了,更严重的是,如果你再用另一个同样会修改componentDidUpdate的HOC增强它,那么前面的HOC就会失效,同时这个HOC也无法应用于没有生命周期的函数组件。
修改传入组件的HOC是一种糟糕的抽象方式,调用者必须知道他们是如何实现的,以避免与其他HOC发生冲突。HOC不应该修改传入组件,而应该使用组合的方式,通过将组件包装在容器组件中实现功能。

function logProps(WrappedComponent) { return class extends React.Component { componentDidUpdate(prevProps) { console.log("Current props: ", this.props); console.log("Previous props: ", prevProps); } render() { // 将 input 组件包装在容器中,而不对其进行修改,Nice! return <WrappedComponent {...this.props} />; } } } 过滤props

HOC为组件添加特性,自身不应该大幅改变约定,HOC返回的组件与原组件应保持类似的接口。HOC应该透传与自身无关的props,大多数HOC都应该包含一个类似于下面的render方法。

render() { // 过滤掉额外的 props,且不要进行透传 const { extraProp, ...passThroughProps } = this.props; // 将 props 注入到被包装的组件中。 // 通常为 state 的值或者实例方法。 const injectedProp = someStateOrInstanceMethod; // 将 props 传递给被包装组件 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); } 最大化可组合性

并不是所有的HOC都一样,有时候它仅接受一个参数,也就是被包裹的组件。

const NavbarWithRouter = withRouter(Navbar);

HOC通常可以接收多个参数,比如在Relay中HOC额外接收了一个配置对象用于指定组件的数据依赖。

const CommentWithRelay = Relay.createContainer(Comment, config);

最常见的HOC签名如下,connect是一个返回高阶组件的高阶函数。

// React Redux 的 `connect` 函数 const ConnectedComment = connect(commentSelector, commentActions)(CommentList); // connect 是一个函数,它的返回值为另外一个函数。 const enhance = connect(commentListSelector, commentListActions); // 返回值为 HOC,它会返回已经连接 Redux store 的组件 const ConnectedComment = enhance(CommentList);

这种形式可能看起来令人困惑或不必要,但它有一个有用的属性,像connect函数返回的单参数HOC具有签名Component => Component,输出类型与输入类型相同的函数很容易组合在一起。同样的属性也允许connect和其他HOC承担装饰器的角色。此外许多第三方库都提供了compose工具函数,包括lodash、Redux和Ramda。

const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent)) // 你可以编写组合工具函数 // compose(f, g, h) 等同于 (...args) => f(g(h(...args))) const enhance = compose( // 这些都是单参数的 HOC withRouter, connect(commentSelector) ) const EnhancedComponent = enhance(WrappedComponent) 不要在render方法中使用HOC

React的diff算法使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树,如果从render返回的组件与前一个渲染中的组件相同===,则React通过将子树与新子树进行区分来递归更新子树,如果它们不相等,则完全卸载前一个子树。
通常在使用的时候不需要考虑这点,但对HOC来说这一点很重要,因为这代表着你不应在组件的render方法中对一个组件应用HOC。

render() { // 每次调用 render 函数都会创建一个新的 EnhancedComponent // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作! return <EnhancedComponent />; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxpwg.html