如何将React组件的实例变量传入其高阶组件(HOC)?
如何在React高阶组件(HOC)中获取增强组件的实例引用?
你之前用组件组合实现可选功能的思路很清晰,转用HOC时遇到的ref传递问题确实是这类场景里的常见卡点。其实有两种比较优雅的方式可以解决这个问题,我给你详细拆解下:
方法一:通过Props传递Ref回调(推荐)
这种方法不需要依赖组件实例,完全通过React的props数据流来传递ref,耦合度更低。核心思路是让HOC提供一个回调方法,子组件把需要绑定交互的元素ref通过这个回调传给HOC。
改造后的Selectable HOC
import React, { Component } from 'react' export default function makeSelectable() { return function decorateComponent(WrappedComponent) { return class Selectable extends Component { constructor(props) { super(props) this.interactionEl = null this.state = { selected: false } this.onClick = this.onClick.bind(this) } onClick(e) { const newSelectedState = !this.state.selected this.setState({ selected: newSelectedState }) // 触发父组件传入的选中回调(如果有的话) if (this.props.onSelected) { this.props.onSelected(newSelectedState) } } // 供子组件调用的回调,用来传递交互元素的ref setInteractionRef = (ref) => { // 先移除之前的事件监听(避免内存泄漏) if (this.interactionEl) { this.interactionEl.removeEventListener('click', this.onClick) } this.interactionEl = ref // 给新的元素绑定点击事件 if (ref) { ref.addEventListener('click', this.onClick) } } componentWillUnmount() { // 组件卸载时清理事件监听 if (this.interactionEl) { this.interactionEl.removeEventListener('click', this.onClick) } } render() { // 把回调方法和选中状态通过props传给子组件 return ( <WrappedComponent {...this.props} setInteractionRef={this.setInteractionRef} selected={this.state.selected} /> ) } } } }
改造后的CanvasElement组件
import React, { Component } from 'react' import Selectable from '../enhancers/Selectable' import flow from 'lodash.flow' import './CanvasElement.css' class CanvasElement extends Component { render() { // 直接把HOC传来的setInteractionRef作为div的ref属性 return ( <div ref={this.props.setInteractionRef} className={`canvas-element ${this.props.selected ? 'selected' : ''}`} > Select me </div> ) } } export default flow( Selectable() )(CanvasElement)
这种方式的优势是:子组件不需要暴露任何内部方法或实例,完全通过props和HOC交互,符合React的单向数据流原则,也更容易维护。
方法二:通过Ref获取子组件实例(需要子组件配合)
如果你的场景更依赖组件实例,可以让子组件暴露一个获取ref的方法,HOC通过ref获取子组件实例后调用该方法拿到交互元素。
改造后的CanvasElement组件
import React, { Component } from 'react' import Selectable from '../enhancers/Selectable' import flow from 'lodash.flow' import './CanvasElement.css' class CanvasElement extends Component { constructor(props) { super(props) this.interactionElRef = React.createRef() } // 暴露给HOC的方法,用来获取交互元素的ref getInteractionElement() { return this.interactionElRef.current } render() { return ( <div ref={this.interactionElRef} className={`canvas-element ${this.props.selected ? 'selected' : ''}`} > Select me </div> ) } } export default flow( Selectable() )(CanvasElement)
改造后的Selectable HOC
import React, { Component } from 'react' export default function makeSelectable() { return function decorateComponent(WrappedComponent) { return class Selectable extends Component { constructor(props) { super(props) this.wrappedInstance = null this.state = { selected: false } this.onClick = this.onClick.bind(this) } onClick(e) { const newSelectedState = !this.state.selected this.setState({ selected: newSelectedState }) if (this.props.onSelected) { this.props.onSelected(newSelectedState) } } componentDidMount() { // 通过子组件实例的方法获取交互元素 const interactionEl = this.wrappedInstance.getInteractionElement() if (interactionEl) { interactionEl.addEventListener('click', this.onClick) } } componentWillUnmount() { const interactionEl = this.wrappedInstance.getInteractionElement() if (interactionEl) { interactionEl.removeEventListener('click', this.onClick) } } render() { // 通过ref获取子组件实例 return ( <WrappedComponent {...this.props} selected={this.state.selected} ref={(instance) => { this.wrappedInstance = instance }} /> ) } } } }
这种方法需要子组件主动暴露方法,耦合度比第一种高一些,但适合某些必须依赖组件实例逻辑的场景。
两种方法都能完美解决你遇到的问题,我个人更推荐第一种,因为它更符合React的设计理念,也更灵活。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

