我有两个组成部分:

父组件 子组件

我试图从Parent调用Child的方法,我尝试了这种方式,但不能得到一个结果:

class Parent extends Component {
  render() {
    return (
      <Child>
        <button onClick={Child.getAlert()}>Click</button>
      </Child>
      );
    }
  }

class Child extends Component {
  getAlert() {
    alert('clicked');
  }
 
  render() {
    return (
      <h1 ref="hello">Hello</h1>
    );
  }
}

是否有一种方法从父调用子方法?

注意:子组件和父组件在两个不同的文件中。


当前回答

如果你这样做只是因为你想让Child为它的父对象提供一个可重用的trait,那么你可以考虑使用render-props来代替。

这种技术实际上把结构颠倒过来了。Child现在包装了父对象,所以我将它重命名为AlertTrait。为了保持连续性,我保留了Parent这个名字,尽管它现在已经不是一个真正的Parent了。

// Use it like this:

  <AlertTrait renderComponent={Parent}/>


class AlertTrait extends Component {
  // You will need to bind this function, if it uses 'this'
  doAlert() {
    alert('clicked');
  }
  render() {
    return this.props.renderComponent({ doAlert: this.doAlert });
  }
}

class Parent extends Component {
  render() {
    return (
      <button onClick={this.props.doAlert}>Click</button>
    );
  }
}

在这种情况下,AlertTrait提供了一个或多个特征,它将这些特征作为道具传递给renderComponent道具中给定的任何组件。

Parent接收doAlert作为道具,并在需要时调用它。

(为了清晰起见,我在上面的例子中调用道具renderComponent。但在上面链接的React文档中,他们只称之为渲染。)

Trait组件可以在它的渲染函数中渲染父元素周围的东西,但它不渲染父元素内部的任何东西。实际上,如果它将另一个道具(例如renderChild)传递给父对象,父对象就可以在它的渲染方法中使用它,它就可以在父对象内部渲染东西。

这与OP要求的有些不同,但有些人可能会在这里结束(就像我们所做的那样),因为他们想要创建一个可重用的trait,并且认为子组件是实现这一点的好方法。

其他回答

首先,让我声明一下,在React领域,这通常不是做事的方式。通常,您要做的是将功能传递给道具中的子元素,并在事件中传递子元素的通知(或者更好的方法是:分派)。

但如果必须在子组件上公开命令式方法,则可以使用refs。记住,这是一个逃生口,通常表明有更好的设计可用。

以前,引用只支持基于类的组件。 随着React Hooks的出现,这种情况不再存在

Modern React with Hooks (v16.8+)

const { forwardRef, useRef, useImperativeHandle } = React; // We need to wrap component in `forwardRef` in order to gain // access to the ref object that is assigned using the `ref` prop. // This ref is passed as the second parameter to the function component. const Child = forwardRef((props, ref) => { // The component instance will be extended // with whatever you return from the callback passed // as the second argument useImperativeHandle(ref, () => ({ getAlert() { alert("getAlert from Child"); } })); return <h1>Hi</h1>; }); const Parent = () => { // In order to gain access to the child component instance, // you need to assign it to a `ref`, so we call `useRef()` to get one const childRef = useRef(); return ( <div> <Child ref={childRef} /> <button onClick={() => childRef.current.getAlert()}>Click</button> </div> ); }; ReactDOM.render( <Parent />, document.getElementById('root') ); <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <div id="root"></div>

useImperativeHandle()的文档在这里:

useImperativeHandle自定义使用ref时暴露给父组件的实例值。

使用类组件的遗留API (>= react@16.4)

const { Component } = React; class Parent extends Component { constructor(props) { super(props); this.child = React.createRef(); } onClick = () => { this.child.current.getAlert(); }; render() { return ( <div> <Child ref={this.child} /> <button onClick={this.onClick}>Click</button> </div> ); } } class Child extends Component { getAlert() { alert('getAlert from Child'); } render() { return <h1>Hello</h1>; } } ReactDOM.render(<Parent />, document.getElementById('root')); <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <div id="root"></div>

回调参考API

回调风格的引用是实现这一点的另一种方法,尽管在现代React中不太常见:

const { Component } = React; const { render } = ReactDOM; class Parent extends Component { render() { return ( <div> <Child ref={instance => { this.child = instance; }} /> <button onClick={() => { this.child.getAlert(); }}>Click</button> </div> ); } } class Child extends Component { getAlert() { alert('clicked'); } render() { return ( <h1>Hello</h1> ); } } render( <Parent />, document.getElementById('app') ); <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="app"></div>

这里是我的演示:https://stackblitz.com/edit/react-dgz1ee?file=styles.css

I am using useEffect to call the children component's methods. I have tried with Proxy and Setter_Getter but sor far useEffect seems to be the more convenient way to call a child method from parent. To use Proxy and Setter_Getter it seems there is some subtlety to overcome first, because the element firstly rendered is an objectLike's element through the ref.current return => <div/>'s specificity. Concerning useEffect, you can also leverage on this approach to set the parent's state depending on what you want to do with the children.

在我提供的演示链接中,你会发现我完整的ReactJS代码和我的草稿,所以你可以欣赏我的解决方案的工作流程。

在这里,我只提供了相关代码的ReactJS片段。:

import React, {
  Component,
  createRef,
  forwardRef,
  useState,
  useEffect
} from "react"; 

{...}

// Child component
// I am defining here a forwardRef's element to get the Child's methods from the parent
// through the ref's element.
let Child = forwardRef((props, ref) => {
  // I am fetching the parent's method here
  // that allows me to connect the parent and the child's components
  let { validateChildren } = props;
  // I am initializing the state of the children
  // good if we can even leverage on the functional children's state
  let initialState = {
    one: "hello world",
    two: () => {
      console.log("I am accessing child method from parent :].");
      return "child method achieve";
    }
  };
  // useState initialization
  const [componentState, setComponentState] = useState(initialState);
  // useEffect will allow me to communicate with the parent
  // through a lifecycle data flow
  useEffect(() => {
    ref.current = { componentState };
    validateChildren(ref.current.componentState.two);
  });

{...}

});

{...}

// Parent component
class App extends Component {
  // initialize the ref inside the constructor element
  constructor(props) {
    super(props);
    this.childRef = createRef();
  }

  // I am implementing a parent's method
  // in child useEffect's method
  validateChildren = childrenMethod => {
    // access children method from parent
    childrenMethod();
    // or signaling children is ready
    console.log("children active");
  };

{...}
render(){
       return (
          {
            // I am referencing the children
            // also I am implementing the parent logic connector's function
            // in the child, here => this.validateChildren's function
          }
          <Child ref={this.childRef} validateChildren={this.validateChildren} />
        </div>
       )
}

你可以很容易地使用你的子组件作为react自定义钩子来应用这个逻辑。

如何实施?

子函数返回一个函数。 子函数返回JSON:{函数、HTML或其他值}作为示例。

在这个例子中,应用这个逻辑没有意义,但很容易看出:

const {useState} = React; //Parent const Parent = () => { //custome hook const child = useChild(); return ( <div> {child.display} <button onClick={child.alert}> Parent call child </button> {child.btn} </div> ); }; //Child const useChild = () => { const [clickCount, setClick] = React.useState(0); {/* child button*/} const btn = ( <button onClick={() => { setClick(clickCount + 1); }} > Click me </button> ); return { btn: btn, //function called from parent alert: () => { alert("You clicked " + clickCount + " times"); }, display: <h1>{clickCount}</h1> }; }; const rootElement = document.getElementById("root"); ReactDOM.render(<Parent />, rootElement); <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

我认为调用方法的最基本方法是在子组件上设置请求。然后,一旦子进程处理了请求,它就调用一个回调方法来重置请求。

为了能够多次发送相同的请求,重置机制是必要的。

在父组件中

在父类的渲染方法中:

const { request } = this.state;
return (<Child request={request} onRequestHandled={()->resetRequest()}/>);

父节点需要两个方法,在两个方向上与子节点进行通信。

sendRequest() {
  const request = { param: "value" };
  this.setState({ request });
}

resetRequest() {
  const request = null;
  this.setState({ request });
}

在子组件中

子进程更新其内部状态,复制来自道具的请求。

constructor(props) {
  super(props);
  const { request } = props;
  this.state = { request };
}

static getDerivedStateFromProps(props, state) {
  const { request } = props;
  if (request !== state.request ) return { request };
  return null;
}

最后,它处理请求,并将重置发送给父进程:

componentDidMount() {
  const { request } = this.state;
  // todo handle request.

  const { onRequestHandled } = this.props;
  if (onRequestHandled != null) onRequestHandled();
}

我希望我没有重复上面的内容但是如果传递一个回调道具来设置父类中的函数呢?这很有效,也很简单。(添加的代码在////之间)

class Parent extends Component {
  ///// 
  getAlert = () => {} // initial value for getAlert

  setGetAlertMethod = (newMethod) => {
    this.getAlert = newMethod;
  }
  /////

  render() {
    return (
      <Child setGetAlertMethod={this.setGetAlertMethod}>
        <button onClick={this.getAlert}>Click</button>
      </Child>
      );
    }
  }



class Child extends Component {
  /////
  componentDidMount() {
    this.props.setGetAlertMethod(this.getAlert);
  }
  /////

  getAlert() => {
    alert('clicked');
  }

  render() {
    return (
      <h1 ref="hello">Hello</h1>
    );
  }
}