从父母调用子方法

Evo*_* SL 358 javascript reactjs

我有两个组成部分.

  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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法从父母那里调用孩子的方法?

注意:Child和Parent组件位于两个不同的文件中

ros*_*dia 518

首先,请允许我表示,这通常不是 React土地上的事情.通常,您要做的是将功能传递给道具中的子项,并在事件中传递来自子项的通知.

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

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

使用挂钩和功能组件(dispatch)

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')
);
Run Code Online (Sandbox Code Playgroud)

功能实例

文档>= react@16.8在这里:

useImperativeHandle()自定义使用时向父组件公开的实例值useImperativeHandle.

使用类组件

<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>
Run Code Online (Sandbox Code Playgroud)

功能示例

旧版API(ref)

出于历史目的,这里是您在16.3之前使用React版本的基于回调的样式:

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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 我累了,但最终得到这个错误"_this2.refs.child.getAlert不是一个函数" (21认同)
  • 那是因为`connect`返回一个包含原始实例的更高阶的组件.您需要首先在连接的组件上调用`getWrappedInstance()`来获取原始组件.然后你可以调用实例方法. (17认同)
  • 如果最佳实践是创建一个逻辑迷宫来做一些像调用子组件的方法那样简单的事情 - 那么我不同意最佳实践. (16认同)
  • 这不是一个好的模式.更不用说字符串引用是不受欢迎的.最好将props传递给子组件,然后在父组件中单击按钮更改父组件的状态,并将状态项传递给子组件,该子组件将触发子组件的"componentWillReceiveProps",并将其用作触发器. (15认同)
  • 不,它不是_usually_最好的模式,它更像是一个逃生舱,当你需要它时,应该只在紧急情况下使用.此外,这个答案是在字符串引用仍然存在的情况下编写的,你说这些日子里它们并不是"正确"做事的方式. (7认同)
  • 使用类比使用新的钩子函数组件要容易得多。我不敢相信没有比上面使用“forwardRef”的钩子示例更简单的方法了。搞得一团糟! (5认同)
  • 这是一个很好的答案,对我来说效果很好。然而,该线程缺少关于为什么这种“不好的做法”的可靠解释。我明白了,“反应团队是这么说的”和“使用 redux”“使用事件”......听起来很复杂。对子函数的引用对我来说很有意义。我在通用模态包装器内有一个动态生成的表单,然后我希望一旦子表单验证自身,父模态提交就从禁用变为启用。它只需要是某种双向街道,我没有听到为什么这是实现这一目标的糟糕方法的充分理由。 (5认同)
  • 对于那些得到`_this2.clickChild不是函数`的人来说这是ref的正确应用:https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-252969542 (4认同)
  • @rossipedia 事实并非如此。事实上,不鼓励只使用代码答案。对“为什么”的简短解释总是受到鼓励,并且对于帮助某人成为更好的程序员而不仅仅是复制粘贴忍者大有帮助。 (3认同)
  • @ThomasSegato,您可以在父组件中编写切换函数,设置切换变量并将该变量作为道具发送到每个子组件。不好的做法是在子组件中编写该切换函数并尝试从父组件中触发它。此方法仅适用于您绝对需要从父组件触发子函数的某些情况。我自己也遇到过一个,这个方法对我很有帮助。 (3认同)
  • 这就是为什么我不会使用 React 的原因。你尝试了 5 分钟,每次都会立即遇到这个问题。我不知道为什么他们不能承认这是一个糟糕的设计模式。如果无法调用子对象的方法,那么首先采用 OO 概念的意义何在?他们总是暗示有一种“反应方式”可以做到这一点,但根据我的经验,通常是没有的。 (3认同)
  • @rossipedia,您说_“通常,您想要做的是将功能传递给道具中的孩子,并传递事件中孩子的通知。” _您能否提供此部分的React文档链接?在事件中,我没有发现孩子发出通知的部分。非常感谢你! (2认同)
  • 为什么这是不好的做法?假设您有一个树视图控件。每个节点都是一个子组件。但您希望能够折叠父节点中的所有节点。超正常的业务场景。如果不给孩子们送东西,你会如何实现这一点? (2认同)
  • 只是通知一下,根据 React 团队的说法 =&gt; 在大多数情况下应该避免使用 refs 的命令式代码。 (2认同)

小智 110

您可以在此处使用其他模式:

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>
  );
 }
}
Run Code Online (Sandbox Code Playgroud)

它的作用是在挂载子进程时设置父进程的clickChild方法.这样,当您单击父级中的按钮时,它将调用clickChild,该单元调用child的getAlert.

如果您的孩子用connect()包装,这也适用,因此您不需要getWrappedInstance()hack.

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

  • 我得到`_this2.clickChild不是函数`为什么? (5认同)
  • 既没有奏效.只有这个答案有效:https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-252969542 (5认同)
  • 这是一项有趣的技术.它非常干净,似乎没有违反任何规则.但我确实认为如果你添加了一个绑定,你的答案会更完整(并满足期望).我非常喜欢这个答案,我已经在[这个相关的Github问题]上发布了它(https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-390556015). (2认同)
  • 这应该是公认的答案 (2认同)

Kas*_*sra 99

在这里我将向您提供四种可能发生的组合:

  1. 班级家长| 钩子
  2. 钩子父| 班级儿童
  3. 钩子父| 钩子
  4. 班级家长| 班级儿童

1.班级家长| 钩子

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }

  render() {
    return (<View>
      <Child ref={this.myRef}/>
      <Button title={'call me'}
              onPress={() => this.myRef.current.childMethod()}/>
    </View>)
  }
}

const Child = React.forwardRef((props, ref) => {

  useImperativeHandle(ref, () => ({
    childMethod() {
      childMethod()
    }
  }))

  function childMethod() {
    console.log('call me')
  }

  return (<View><Text> I am a child</Text></View>)
})
Run Code Online (Sandbox Code Playgroud)

2. 钩住父| 班级儿童

function Parent(props) {

  const myRef = useRef()

  return (<View>
    <Child ref={myRef}/>
    <Button title={'call me'}
            onPress={() => myRef.current.childMethod()}/>
  </View>)
}

class Child extends React.Component {

  childMethod() {
    console.log('call me')
  }

  render() {
    return (<View><Text> I am a child</Text></View>)
  }
}
Run Code Online (Sandbox Code Playgroud)

3. 钩住父| 钩子

function Parent(props) {

  const myRef = useRef()

  return (<View>
    <Child ref={myRef}/>
    <Button title={'call me'}
            onPress={() => myRef.current.childMethod()}/>
  </View>)
}

const Child = React.forwardRef((props, ref) => {

  useImperativeHandle(ref, () => ({
    childMethod() {
      childMethod()
    }
  }))

  function childMethod() {
    console.log('call me')
  }

  return (<View><Text> I am a child</Text></View>)
})
Run Code Online (Sandbox Code Playgroud)

4.班级家长| 班级儿童

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }

  render() {
    return (<View>
      <Child ref={this.myRef}/>
      <Button title={'call me'}
              onPress={() => this.myRef.current.childMethod()}/>
    </View>)
  }
}

class Child extends React.Component {

  childMethod() {
    console.log('call me')
  }

  render() {
    return (<View><Text> I am a child</Text></View>)
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是真棒。干净整洁的答案。我使用了类| 上课,工作顺利。其余 3 种类型也很有用。 (2认同)

ton*_*ral 72

使用 useEffect 的替代方法:

家长:

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

孩子们:

useEffect(() => {
    performRefresh(); //children function of interest
  }, [props.refresh]);
Run Code Online (Sandbox Code Playgroud)

  • 诗。如果您的愿望只是重新渲染表单(例如,重置输入字段),那么您甚至不需要包含 useEffect,您只需将发送到组件中的 prop 更改即可 (6认同)
  • 这很好用。请注意,您还可以检查 `(props.refresh !== 0)` 以避免在初始循环上运行该函数。 (4认同)
  • 该解决方案非常适合同时刷新或调用多个子项的函数! (2认同)

小智 28

https://facebook.github.io/react/tips/expose-component-functions.html 获取更多答案ref here 在React子组件上调用方法

通过查看"原因"组件的引用,您将破坏封装并使得无法在不仔细检查其使用的所有位置的情况下重构该组件.因此,我们强烈建议将refs视为组件的私有,就像状态一样.

通常,数据应该通过props传递到树下.这有一些例外(例如调用.focus()或触发一次性动画并不真正"改变"状态)但是当你暴露一个名为"set"的方法时,通常会出现道具一个更好的选择.尝试做到这一点,以便内部输入组件担心它的大小和外观,以便它的祖先没有.

  • 与 props 相比,这究竟是如何破坏封装的? (6认同)
  • 以下是这个答案的来源:https://discuss.reactjs.org/t/is-it-bad-practice-to-call-child-component-methods-from-parent/1821/2.引用他人没有问题,但至少提出了一些参考. (4认同)

Bla*_*mba 18

我正在使用useEffecthook 来克服执行这一切的麻烦,所以现在我将一个变量传递给 child,如下所示:

import { useEffect, useState } from "react";

export const ParentComponent = () => {
  const [trigger, setTrigger] = useState(false);

  return (
    <div onClick={() => { setTrigger(trigger => !trigger); }}>
      <ChildComponent trigger={trigger}></ChildComponent>
    </div>
  );
};

export const ChildComponent = (props) => {
  const triggerInvokedFromParent = () => {
    console.log('TriggerInvokedFromParent');
  };

  useEffect(() => {
    triggerInvokedFromParent();
  }, [props.trigger]);

  return <span>ChildComponent</span>;
};
Run Code Online (Sandbox Code Playgroud)


Pet*_*rth 13

我希望我没有重复上面的任何内容,但是传递一个在父级中设置函数的回调道具呢?这很有效,而且很容易。(添加的代码位于 //// 之间)

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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)


And*_*Dev 12

我对这里提供的任何解决方案都不满意。实际上有一个非常简单的解决方案,可以使用纯 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>;
}
Run Code Online (Sandbox Code Playgroud)

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

对上述代码的改进是不将父函数和子函数直接添加到 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>;
}
Run Code Online (Sandbox Code Playgroud)

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

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


Roh*_*rte 12

对于功能组件最简单的方法是

父组件

父级.tsx

import React, { useEffect, useState, useRef } from "react";
import child from "../../child"

const parent: React.FunctionComponent = () => {
  const childRef: any = useRef();
    
const onDropDownChange: any = (event): void => {
    const target = event.target;
    childRef.current.onFilterChange(target.value);
};

return <child ref={childRef} />
}
export default parent;
Run Code Online (Sandbox Code Playgroud)

子组件

孩子.tsx

import React, {   useState,   useEffect,   forwardRef,   useRef,   useImperativeHandle, } from "react";

const Child = forwardRef((props, ref) => {
 useImperativeHandle(ref, () => ({
    onFilterChange(id) {
      console.log("Value from parent", id)
    },
  }));
})

Child.displayName = "Child";

export default Child;
Run Code Online (Sandbox Code Playgroud)


Muh*_*man 9

您可以使用ref从父级调用子组件的功能

功能组件解决方案

在功能组件中,您必须使用useImperativeHandleref 进入像下面这样的孩子

import React, { forwardRef, useRef, useImperativeHandle } from 'react';
export default function ParentFunction() {
    const childRef = useRef();
    return (
        <div className="container">
            <div>
                Parent Component
            </div>
            <button
                onClick={() => { childRef.current.showAlert() }}
            >
            Call Function
            </button>
            <Child ref={childRef}/>
        </div>
    )
}
const Child = forwardRef((props, ref) => {
    useImperativeHandle(
        ref,
        () => ({
            showAlert() {
                alert("Child Function Called")
            }
        }),
    )
    return (
       <div>Child Component</div>
    )
})
Run Code Online (Sandbox Code Playgroud)

类组件解决方案

儿童.js

import s from './Child.css';

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

export default Child;
Run Code Online (Sandbox Code Playgroud)

父.js

class Parent extends Component {
 render() {
  onClick() {
    this.refs.child.getAlert();
  }
  return (
    <div>
      <Child ref="child" />
      <button onClick={this.onClick}>Click</button>
    </div>
  );
 }
}
Run Code Online (Sandbox Code Playgroud)


joe*_*dle 8

如果您这样做仅仅是因为您希望 Child 为其父母提供可重用的特征,那么您可以考虑使用 render-props来做到这一点。

该技术实际上将结构颠倒了。在Child现在包装了父母,所以我改名到AlertTrait下面。我保留了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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

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

ParentdoAlert作为 prop接收,可以在需要时调用它。

(为了清楚起见,我renderComponent在上面的示例中调用了 prop 。但是在上面链接的 React 文档中,他们只是调用了它render。)

Trait 组件可以在其渲染函数中渲染 Parent 周围的东西,但它不会在父级内部渲染任何东西。实际上它可以在 Parent 内部渲染东西,如果它传递另一个 prop(例如renderChild)给父级,然后父级可以在其渲染方法中使用。

这与 OP 要求的有些不同,但有些人可能会在这里(就像我们一样)因为他们想要创建一个可重用的特征,并认为子组件是一个很好的方法来做到这一点。


S.Y*_*dav 8

我们可以通过其他方式使用refs-

我们将创建一个Parent元素,它将渲染一个<Child/>组件。如您所见,将要渲染的组件需要添加ref属性并为其提供名称。
然后,triggerChildAlert位于父类中的函数将访问此上下文的refs属性(当triggerChildAlert触发该函数时,将访问子引用,并将具有子元素的所有功能)。

class Parent extends React.Component {
    triggerChildAlert(){
        this.refs.child.callChildMethod();
        // to get child parent returned  value-
        // this.value = this.refs.child.callChildMethod();
        // alert('Returned value- '+this.value);
    }

    render() {
        return (
            <div>
                {/* Note that you need to give a value to the ref parameter, in this case child*/}
                <Child ref="child" />
                <button onClick={this.triggerChildAlert}>Click</button>
            </div>
        );
    }
}  
Run Code Online (Sandbox Code Playgroud)

现在,按照先前的理论设计,子组件将如下所示:

class Child extends React.Component {
    callChildMethod() {
        alert('Hello World');
        // to return some value
        // return this.state.someValue;
    }

    render() {
        return (
            <h1>Hello</h1>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

这是源代码-
希望能为您服务!

  • 字符串引用已被弃用。https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs (2认同)

Ami*_*aei 8

此模式类似于@brickingup答案。但在此版本中,您可以设置任意数量的子操作。

import { useEffect } from "react";

export const Parent = () => {
  const childEvents = { click: () => {} };

  return (
    <div onClick={() => childEvents.click()}>
      <Child events={childEvents}></Child>
    </div>
  );
};

export const Child = (props) => {
  const click = () => {
    alert("click from child");
  };

  useEffect(() => {
    if (props.events) {
      props.events.click = click;
    }
  }, []);

  return <span>Child Component</span>;
};
Run Code Online (Sandbox Code Playgroud)


Has*_*Eqx 7

逻辑很简单。

Create a function in parent using child or use ref.

我更喜欢父级使用子级的创建功能。有多种方法可以做到这一点。

使用功能组件时

在家长中

function Parent(){
  const [functionToCall, createFunctionToCall] = useState(()=>()=>{})

  return (
   <Child createFunctionToCall={createFunctionToCall} />
  )
}
Run Code Online (Sandbox Code Playgroud)

儿童时期

function Child({createFunctionToCall}){
  useEffect(()=>{
    function theFunctionToCall(){
      // do something like setting something
      // don't forget to set dependancies properly.
    }
    createFunctionToCall(()=>theFunctionToCall)
  },[createFunctionToCall])
}

Run Code Online (Sandbox Code Playgroud)