Unable to access child function via ref?

2020-03-03 08:46发布

问题:

Initially everything was working fine,I have a component something like. this

  class A extends React.Component {
     constructor(props) {
       super(props);
       this.childRef = null
     }

    componentDidMount() {
     this.childRef = this.refs.b
     // now I can call child function like this
      this.childRef.calledByParent()
    }

    render(){
      <B ref = "b"/>
    }
  }

In other file

     class B extends React.Component {

       calledByParent(){
         console.log("i'm called")
        }

       render(){
        <div> hello </div>
       }
  }
 export default B

till here it was working fine but when I do something like this in class B export default connect(mapStateToProps, mapDispatchToProps)(B)

It is not working. I have imported connect from react-redux

回答1:

connect() accepts option as the forth parameter. In this option parameter you can set flag withRef to true. After this you can access functions to refs by using getWrappedInstance() like

class A extends React.Component {
     constructor(props) {
       super(props);
       this.childRef = null
     }

    componentDidMount() {
        this.childRef.getWrappedInstance().calledByParent()
    }

    render(){
      <B ref = {ref => this.childRef = ref}/>
    }
  }

class B extends React.Component {

       calledByParent(){
         console.log("i'm called")
        }

       render(){
        <div> hello </div>
       }
  }
   export default  connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(B)


回答2:

I had similar problem but I didn't want to make my APIs dependent on getWrappedInstance() calls. In fact some components in your class hierarchy may use connect() and access the store and some others are just stateless components that don't need that additional Redux layer.

I have just written a small (maybe a bit hackish) method. Please note, it hasn't been fully tested yet so expect you may need to make some adjustments to get it working in your own scenario.

TypeScript (should be easy to convert to pure JavaScript syntax):

function exposeWrappedMethods(comp: React.ComponentClass<any>, proto?: any): any {
    if (!proto) {
        if (comp.prototype.constructor.name === 'Connect') {
            // Only Redux component created with connect() is supported
            proto = comp.prototype.constructor.WrappedComponent.prototype;
        } else {
            console.warn('Trying to extend an invalid component.');
            return comp;
        }
    }

    let prototypeName: string = proto.constructor.name;
    if (prototypeName.search(/^React.*Component.*/) < 0 && proto.__proto__) {
        for (let propertyName of Object.getOwnPropertyNames(proto)) {
            if (!comp.prototype[propertyName]) {
                let type: string = typeof proto[propertyName];
                if (type === 'function') {
                    // It's a regular function
                    comp.prototype[propertyName] = function (...args: any[]) {
                        return this.wrappedInstance[propertyName](args);
                    };
                } else if (type === 'undefined') {
                    // It's a property
                    Object.defineProperty(comp.prototype, propertyName, {
                        get: function () {
                            return (this as any).wrappedInstance[propertyName];
                        },
                        set: function (value: any) {
                            (this as any).wrappedInstance[propertyName] = value;
                        }
                    });
                }
            }
        }

        return exposeWrappedMethods(comp, proto.__proto__);
    }

    return comp;
}

Use it by simply wrapping your connect() call with exposeWrappedMethods. It will add all methods and properties from your own class (and subclasses) but will not overwrite already existing methods (i.e. methods from React.Component base class).

export default exposeWrappedMethods(
    connect<any, any, Properties>(
        (state: ApplicationState) => state.counter,
        CounterState.actionCreators,
        null,
        { pure: false, withRef: true } // It requires use of "withRef: true"
    )(Counter)) as typeof Counter;

Hope you (or someone else) find it useful.

/Lukasz



回答3:

Might be a little late but another (better) solution than using refs is to only give control to specific functions of the component.

class A extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        this.ctrl_B.calledByParent()
    }

    render(){
        <B provideCtrl={ctrl => this.ctrl_B = ctrl} />
    }
}

class B extends React.Component {

    componentDidMount() {
        this.props.provideCtrl({
            calledByParent: () => this.calledByParent()
        });
    }
    componentWillUnmount() {
        this.props.provideCtrl(null);
    }

    calledByParent(){
        console.log("i'm called")
    }

    render(){
        <div> hello </div>
    }
}
export default  connect(mapStateToProps, mapDispatchToProps)(B)