从父级调用子方法

IT技术 javascript reactjs
2021-02-06 02:51:14

我有两个组成部分:

  1. 父组件
  2. 子组件

我试图从 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>
    );
  }
}

有没有办法从 Parent 调用 Child 的方法?

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

6个回答

首先,让我表示,这通常不是React 领域的处理方式。通常你想要做的是在 props 中将功能传递给孩子,并在事件中传递来自孩子的通知(或者更好:)dispatch

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

以前,仅基于类的组件支持 refs。随着React Hooks的出现,情况不再如此

使用 Hooks ( v16.8+) 的现代 React

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

回调风格的 refs 是实现这一目标的另一种方法,尽管在现代 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>

不,它通常不是最好的模式,它更像是您需要时的逃生舱口,并且只应在紧急情况下使用。此外,这个答案是在字符串引用仍然存在的时候写的,你是对的,它们现在不是“正确”的做事方式。
2021-03-17 02:51:14
我累了,但最终出现了这个错误“_this2.refs.child.getAlert is not a function”
2021-03-19 02:51:14
如果最佳实践是创建一个逻辑迷宫来做一些像调用子组件的方法一样简单的事情 - 那么我不同意最佳实践。
2021-03-21 02:51:14
那是因为connect返回包装原始实例的高阶组件。您需要首先调用getWrappedInstance()连接的组件来获取原始组件。然后你可以调用实例方法。
2021-03-23 02:51:14
这真的不是一个好的模式。更不用说字符串引用不受欢迎了。最好将 props 传递给子组件,然后在父组件中单击按钮更改父组件的状态,并将状态项传递给将触发子组件的子组件componentWillReceiveProps,并将其用作触发器。
2021-04-06 02:51:14

您可以在此处使用另一种模式:

class Parent extends Component {
 render() {
  return (
    <div>
      <Child setClick={click => this.clickChild = click}/>
      <button onClick={() => this.clickChild()}>Click</button>
    </div>
  );
 }
}

class Child extends Component {
 constructor(props) {
    super(props);
    this.getAlert = this.getAlert.bind(this);
 }
 componentDidMount() {
    this.props.setClick(this.getAlert);
 }
 getAlert() {
    alert('clicked');
 }
 render() {
  return (
    <h1 ref="hello">Hello</h1>
  );
 }
}

它所做的是在clickChild安装子项时设置父项的方法。这样,当您单击 parent 中的按钮时,它将调用clickChild调用 child 的getAlert.

如果您的孩子被包裹,这也有效,connect()因此您不需要getWrappedInstance()黑客。

请注意,您不能onClick={this.clickChild}在父级中使用,因为当父级呈现时,子级尚未安装,因此this.clickChild尚未分配。使用onClick={() => this.clickChild()}很好,因为当你点击按钮时this.clickChild应该已经被分配了。

这应该是公认的答案
2021-03-10 02:51:14
都没有工作。只有这个答案有效:github.com/kriasoft/react-starter-kit/issues/...
2021-03-15 02:51:14
我明白_this2.clickChild is not a function为什么?
2021-03-21 02:51:14
这是一个有趣的技术。它很干净,似乎没有违反任何规则。但是我确实认为如果您添加了绑定,您的答案会更完整(并符合预期)。我非常喜欢这个答案,我已将其发布在此相关的 Github 问题上
2021-03-26 02:51:14
2021-04-04 02:51:14

使用 useEffect 的替代方法:

家长:

const [refresh, doRefresh] = useState(0);
<Button onClick={() => doRefresh(prev => prev + 1)} />
<Children refresh={refresh} />

孩子们:

useEffect(() => {
    performRefresh(); //children function of interest
  }, [props.refresh]);
这很好用。请注意,您还可以检查(props.refresh !== 0)以避免在初始循环中运行该函数。
2021-03-16 02:51:14
附言。如果您只想重新渲染表单(例如,重置输入字段),那么您甚至不需要包含 useEffect,您只需将发送到组件中的 prop 更改即可
2021-03-18 02:51:14
@tonymayoral 有没有一种方法可以让我们在子组件中使用 useState 并从父组件中使用 doRefresh。就我而言,我不希望我的父母重新渲染。
2021-03-31 02:51:14
@MattFletcher 没有useEffect,您可能会收到无限循环
2021-04-01 02:51:14
此解决方案非常适合同时刷新或调用多个子项的函数!
2021-04-07 02:51:14

https://facebook.github.io/react/tips/expose-component-functions.html 有关更多答案请参考此处调用 React 子组件上的方法

通过查看“原因”组件的引用,您破坏了封装,并且如果不仔细检查它使用的所有地方就无法重构该组件。因此,我们强烈建议将 refs 视为组件私有的,就像状态一样。

一般来说,数据应该通过 props 沿着树向下传递。对此有一些例外(例如调用 .focus() 或触发一次并没有真正“改变”状态的动画)但是任何时候你暴露一个叫做“set”的方法,props通常是更好的选择。尽量让内部输入组件担心它的大小和外观,这样它的祖先就不会担心了。

与 props 相比,这究竟是如何打破封装的?
2021-03-19 02:51:14
下面是这个答案的来源:discuss.reactjs.org/t/...引用其他人没有问题,但至少提供了一些参考。
2021-04-06 02:51:14

我对这里提供的任何解决方案都不满意。实际上有一个非常简单的解决方案,可以使用纯 Javascript 完成,而不依赖于基本 props 对象以外的一些 React 功能 - 它为您提供了双向通信的好处(父 -> 子,子 -> 父)。您需要将一个对象从父组件传递给子组件。这个对象就是我所说的“双向引用”或简称 biRef。基本上,该对象包含对父级想要公开的父级方法的引用。并且子组件将方法附加到父组件可以调用的对象上。像这样的东西:

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      someParentFunction: someParentFunction
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef.someChildFunction = someChildFunction;

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

此解决方案的另一个优点是您可以在父和子中添加更多函数,同时仅使用单个属性将它们从父传递给子。

对上述代码的改进是不将父函数和子函数直接添加到 biRef 对象,而是添加到子成员。父函数应该添加到名为“parent”的成员中,而子函数应该添加到名为“child”的成员中。

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.child.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      parent: {
          someParentFunction: someParentFunction
      }
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.parent.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef {
       child: {
            someChildFunction: someChildFunction
       }
   }

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

通过将父函数和子函数放入 biRef 对象的单独成员中,您将清楚地分离两者并轻松查看哪些属于父函数或子函数。如果相同的函数出现在两个子组件中,它还有助于防止子组件意外覆盖父函数。

最后一件事是,如果您注意到,父组件使用 var 创建 biRef 对象,而子组件通过 props 对象访问它。在父级中不定义 biRef 对象并通过其自己的 props 参数从其父级访问它可能很诱人(在 UI 元素的层次结构中可能就是这种情况)。这是有风险的,因为孩子可能认为它正在调用的父函数属于父函数,而实际上它可能属于祖父母。只要你意识到这一点,这并没有错。除非您有理由支持父/子关系之外的某些层次结构,否则最好在您的父组件中创建 biRef。

这很有效,但它是否违反了反应心态?
2021-03-17 02:51:14