从父级调用子级方法


473

我有两个组成部分。

  1. 父组件
  2. 子组件

我试图从父级调用孩子的方法,我尝试过这种方法,但没有得到结果

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>
    );
  }
}

有没有一种方法可以从父级调用子级的方法?

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


尽管我对此很迟,但是我也正在学习React,所以我想知道父母需要调用子方法的情况。你能解释一下吗?
阿克瑟(Akshay Raut)

有谁知道如何从箭头功能做到这一点?stackoverflow.com/questions/60015693/...
托马斯Segato

@AkshayRaut IMO是一个很好的用例:具有重置和提交功能的通用表单,后者会返回表单值。
朱利安·K

Answers:


702

首先,让我表示,这通常不是在React领域中解决问题的方法。通常,您要做的就是在props中将功能传递给子级,并在事件中传递子级的通知(或者更好的是:)dispatch

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

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

使用挂钩和功能组件(>= react@16.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

使用类组件(>= 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@16.3

出于历史目的,这是您在16.3之前的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>


23
我很累,但最终遇到此错误“ _this2.refs.child.getAlert不是函数”
N8FURY

22
那是因为connect返回包裹原始实例的高阶组件。您需要先调用getWrappedInstance()连接的组件以获取原始组件。然后,您可以在其上调用实例方法。
rossipedia

16
这确实不是一个好模式。更不用说字符串引用了。最好将props传递给child组件,然后在父级中单击按钮以更改父级的状态,然后将状态项传递给子级,这将触发child的状态componentWillReceiveProps,并将其用作触发器。
ffxsam

7
不,它通常不是最佳模式,在您需要时它更像是逃生舱口,仅应在紧急情况下使用。同样,这个答案是在字符串引用仍然存在的时候写的,而您说对了,这不是如今的“正确”方法。
rossipedia

34
如果最佳实践是创建一个逻辑迷宫以完成像调用子组件的方法之类的简单操作-那么我不同意最佳实践。
aaaaaa

149

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

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挂载子项时设置父项的方法。这样,当您单击父级中的按钮时,它将调用clickChild哪个调用子级的getAlert

如果您的孩子被包裹着,connect()那么这也适用,因此您不需要getWrappedInstance()黑客。

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


5
我明白_this2.clickChild is not a function为什么?
tatsu

1
请不要介意这个工作对我来说:github.com/kriasoft/react-starter-kit/issues/...

5
都不起作用。只有这个答案有效:github.com/kriasoft/react-starter-kit/issues/…–
tatsu

2
这是一种有趣的技术。这很干净而且似乎没有违反任何规则。但是我确实认为,如果添加了绑定,您的答案将会更加完整(并达到预期)。我非常喜欢这个答案,我已经在相关的Github问题上发布了它。
joeytwiddle

7
这应该是公认的答案
Jesus Gomez

27

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

通过查看“原因”组件的引用,您正在破坏封装并使您无法仔细检查组件的所有使用位置就无法重构该组件。因此,我们强烈建议将ref视为组件的私有属性,就像state一样。

通常,数据应通过道具沿着树传递。有一些例外(例如,调用.focus()或触发一次不会真正“改变”状态的一次性动画),但是每当您公开称为“ set”的方法时,道具通常更好的选择。尝试使其内部输入组件担心其大小和外观,以便其祖先都不做。


5
这是此答案的来源:describe.reactjs.org/t/…。引用他人没有问题,但至少提供了一些参考。
Jodo

1
除道具外,这种破坏封装到底有多精确?
Timmmm

15

useEffect的替代方法:

上级:

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

儿童:

useEffect(() => {
    refresh(); //children function of interest
  }, [props.refresh]);

2
这应该得到更多的选票
阿里·胺

附言 如果你的愿望就是重新呈现形式(例如,复位输入字段),那么你甚至都不需要包括useEffect,你可以让道具被送进成分变化
马特·弗莱彻

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>
        );
    }
}  

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

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

    render() {
        return (
            <h1>Hello</h1>
        );
    }
}

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



4

如果只是因为您希望子项为其父项提供可重用的特征而这样做,则您可以考虑改为使用render-props

该技术实际上使结构颠倒了。在Child现在包装了父母,所以我改名到AlertTrait下面。我保留Parent连续性的名称,尽管现在它实际上并不是父母。

// Use it like this:

  <AlertTrait renderComponent={Parent}/>


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

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

在这种情况下,AlertTrait提供一个或多个特征,将其作为道具传递到道具中指定的任何组件renderComponent

家长收到doAlert作为道具,可以在需要时调用它。

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

Trait组件可以在其render函数中渲染围绕Parent的内容,但不会渲染Parent内部的任何内容。实际上,如果它向父级传递了另一个道具(例如renderChild),则它可以在父级内部渲染事物,然后父级可以在其render方法中使用它。

这与OP要求的有所不同,但有些人可能会像我们一样最终来到这里(因为我们这样做了),因为他们想创建可重用的特征,并认为子组件是实现此目标的好方法。


这里有用于创建可重复使用特征的便捷模式列表:reactjs.org/blog/2016/07/13/…–
joeytwiddle

如果您有N个秒表和一个按钮来重新启动它们,该怎么办。如何在这里使道具方便?
vsync

@vsync我不确定该方法是否可以帮助您完成任务。但是砖砌的答案可能会有所帮助。请注意,它们已设置,this.clickChild = click但是您的多个秒表将传递多个功能,因此您需要存储所有这些功能:this.watchRestartFuncs[watchId] = restartWatch
joeytwiddle19年

1

您可以通过这种方式轻松实现

脚步-

  1. 在父类的状态中创建一个布尔变量。当您要调用函数时,请对其进行更新。
  2. 创建一个prop变量并分配布尔变量。
  3. 从子组件使用props访问该变量,并通过具有if条件来执行所需的方法。

    class Child extends Component {
       Method=()=>{
       --Your method body--
       }
       render() {
         return (
        //check whether the variable has been updated or not
          if(this.props.updateMethod){
            this.Method();
          }
         )
       }
    }
    
    class Parent extends Component {
    
    constructor(){
      this.state={
       callMethod:false
      }
    
    }
    render() {
       return (
    
         //update state according to your requirement
         this.setState({
            callMethod:true
         }}
         <Child updateMethod={this.state.callMethod}></Child>
        );
       }
    }

您可能想对此沙盒化。看起来您将陷入无限循环,因为子方法将继续运行,因为父状态设置为true。
艾萨克·朴

@IsaacPak是的,这就是为什么我在这里发表评论,说您必须根据您的要求更新状态。那么它就不会无限循环地运行。
库萨尔·基斯马尔

1

我正在使用useEffect钩子来克服做所有这些事情的麻烦,所以现在我将一个变量传递给孩子,如下所示:

<ParentComponent>
 <ChildComponent arbitrary={value} />
</ParentComponent>
useEffect(() => callTheFunctionToBeCalled(value) , [value]);

1

我对这里介绍的任何解决方案都不满意。实际上,有一个非常简单的解决方案,可以使用纯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。



0

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

重置机制是必需的,以便能够彼此多次发送相同的请求。

在父组件中

在父级的render方法中:

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

父级需要2种方法,才能在2个方向上与其子级进行通信。

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();
}

0

从父级触发子级功能的另一种方法是利用componentDidUpdate子级Component 中的功能。我triggerChildFunc从父母到孩子传递了一个道具,最初是null。单击按钮时,该值将更改为函数,并且Child会注意到该更改componentDidUpdate并调用其自己的内部函数。

由于prop triggerChildFunc更改为函数,因此我们还会获得对Parent的回调。如果父级不需要知道何时调用该函数,则该值triggerChildFunc可以例如从更改nulltrue

const { Component } = React;
const { render } = ReactDOM;

class Parent extends Component {
  state = {
    triggerFunc: null
  }

  render() {
    return (
      <div>
        <Child triggerChildFunc={this.state.triggerFunc} />
        <button onClick={() => {
          this.setState({ triggerFunc: () => alert('Callback in parent')})
        }}>Click
        </button>
      </div>
    );
  }
}

class Child extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.triggerChildFunc !== prevProps.triggerChildFunc) {
      this.onParentTrigger();
    }
  }

  onParentTrigger() {
    alert('parent triggered me');

    // Let's call the passed variable from parent if it's a function
    if (this.props.triggerChildFunc && {}.toString.call(this.props.triggerChildFunc) === '[object Function]') {
      this.props.triggerChildFunc();
    }
  }

  render() {
    return (
      <h1>Hello</h1>
    );
  }
}


render(
  <Parent />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>

CodePen:https://codepen.io/calsal/pen/NWPxbJv editors = 1010


0

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

useEffect用来调用子组件的方法。我尝试过,Proxy and Setter_Getter但到目前为止,useEffect似乎是从父级调用子级方法的更便捷方法。要使用Proxy and Setter_Getter它,似乎首先要克服一些细微之处,因为首先呈现的元素是通过ref.current return => <div/>的特异性成为objectLike的元素。关于useEffect,您还可以利用此方法来根据要对子级进行的操作来设置父级的状态。

在我提供的演示链接中,您将找到我完整的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>
       )
}

0

我们对调用的自定义钩子感到满意useCounterKey。它只是设置一个counterKey或从零开始计数的密钥。它返回的功能会重置键(即增量)。(我相信这是React中重置组件的最惯用的方法,只需按一下键即可。)

但是,此钩子也可以在您希望向客户端发送一次性消息以执行某项操作的任何情况下使用。例如,我们使用它来将子项中的控件集中在某个父事件上-它仅在密钥更新时自动聚焦。(如果需要更多道具,可以在重置钥匙之前进行设置,以便在事件发生时可以使用。)

这个方法有点学习曲线b / c,它不像典型的事件处理程序那样简单,但是它似乎是我们在React中发现的最惯用的方法(因为键已经以这种方式起作用)。Def希望就此方法提供反馈,但效果很好!

// Main helper hook:
export function useCounterKey() {
  const [key, setKey] = useState(0);
  return [key, () => setKey(prev => prev + 1)] as const;
}

用法示例:

// Sample 1 - normal React, just reset a control by changing Key on demand
function Sample1() {
  const [inputLineCounterKey, resetInputLine] = useCounterKey();

  return <>
    <InputLine key={inputLineCounterKey} />
    <button onClick={() => resetInputLine()} />
  <>;
}

// Second sample - anytime the counterKey is incremented, child calls focus() on the input
function Sample2() {
  const [amountFocusCounterKey, focusAmountInput] = useCounterKey();

  // ... call focusAmountInput in some hook or event handler as needed

  return <WorkoutAmountInput focusCounterKey={amountFocusCounterKey} />
}

function WorkoutAmountInput(props) {
  useEffect(() => {
    if (counterKey > 0) {
      // Don't focus initially
      focusAmount();
    }
  }, [counterKey]);

  // ...
}

counterKey概念版权归 Kent Dodds 所有。)


-1

这是个错误吗?需要注意的地方:我同意使用forwardRef,useRef,useImperativeHandle的rossipedia解决方案

在线上有一些错误信息,指出只能从React Class组件创建引用,但是如果您使用上面提到的钩子,则确实可以使用功能组件。请注意,这些钩子仅在我将文件更改为在导出组件时不与withRouter()一起使用后才起作用。即从

export default withRouter(TableConfig);

代替成为

export default TableConfig;

事后看来,这样的组件并不需要withRouter(),但通常不会伤害到它。我的用例是我创建了一个组件来创建一个Table来处理配置值的查看和编辑,而且我希望能够在单击“父窗体”的“重置”按钮时告诉此“子”组件重置其状态值。直到我从包含子组件TableConfig的文件中删除了withRouter(),UseRef()才能正确获取ref或ref.current(保持为空)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.