818 votes

Appeler la méthode enfant depuis le parent

J'ai deux composants :

  1. Composante parentale
  2. Composante enfant

J'essayais d'appeler la méthode de l'enfant depuis le parent, j'ai essayé de cette façon mais je n'ai pas pu obtenir de résultat :

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

Existe-t-il un moyen d'appeler la méthode de l'enfant depuis le parent ?

Remarque : les composants Enfant et Parent se trouvent dans deux fichiers différents.

1 votes

stackoverflow.com/a/65917432/8079868 pouvez-vous accepter ma réponse ?

0 votes

Vous pouvez faire de l'inversion d'héritage (voir ici) : medium.com/@franleplant/ ). De cette façon, vous avez accès à l'instance du composant que vous allez envelopper (vous pourrez donc accéder à ses fonctions).

1198voto

rossipedia Points 10922

Tout d'abord, laissez-moi exprimer que c'est généralement no la façon de faire les choses à React land. En général, ce que vous voulez faire, c'est transmettre des fonctionnalités aux enfants dans des props, et transmettre des notifications des enfants dans des événements (ou mieux encore : dispatch ).

Mais si vous debe exposer une méthode impérative sur un composant enfant, vous pouvez utiliser la fonction refs . N'oubliez pas qu'il s'agit d'une trappe d'évacuation et que cela indique généralement qu'une meilleure conception est disponible.

Auparavant, les références n'étaient prises en charge que pour les composants basés sur des classes. Avec l'arrivée de Crochets React ce n'est plus le cas

React moderne avec 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>

Documentation pour useImperativeHandle() es aquí :

useImperativeHandle personnalise la valeur d'instance qui est exposée aux composants parents lors de l'utilisation de l'option ref .

Legacy API utilisant des composants de classe ( >= 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 Callback Ref

Les références de type callback sont une autre approche pour y parvenir, bien qu'elles ne soient pas aussi courantes dans le React moderne :

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>

31 votes

J'ai essayé, mais je me retrouve avec cette erreur "_this2.refs.child.getAlert n'est pas une fonction".

0 votes

Et je dois mentionner que ces composants sont dans deux fichiers séparés et que j'importe l'enfant dans le parent.

0 votes

Dans ce cas, je vous suggère de modifier votre question pour indiquer qu'ils se trouvent dans deux fichiers différents et de publier le code exact qui pose problème.

201voto

brickingup Points 718

Vous pouvez utiliser un autre modèle ici :

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

Ce qu'il fait, c'est de définir la valeur du parent. clickChild lorsque l'enfant est monté. De cette façon, lorsque vous cliquez sur le bouton dans le parent, il appellera clickChild qui appelle l'enfant getAlert .

Cela fonctionne également si votre enfant est enveloppé de connect() donc vous n'avez pas besoin de la getWrappedInstance() pirate.

Notez que vous ne pouvez pas utiliser onClick={this.clickChild} dans le parent car lorsque le parent est rendu, l'enfant n'est pas monté donc this.clickChild n'est pas encore attribué. Utilisation de onClick={() => this.clickChild()} est bien parce que lorsque vous cliquez sur le bouton this.clickChild devrait déjà être attribué.

5 votes

Je reçois _this2.clickChild is not a function Pourquoi ?

1 votes

C'est pas grave, ça a marché pour moi : github.com/kriasoft/react-starter-kit/issues/

0 votes

Avez-vous utilisé onClick={this.clickChild} ou onClick={() => this.clickChild()} ? La première approche ne fonctionnera pas pour la raison indiquée dans la partie "Note...." de ma réponse. L'approche ref ne fonctionnera pas si vous enveloppez votre composant dans connect() de redux.

35voto

Mike Tronic Points 384

https://facebook.github.io/react/tips/expose-component-functions.html pour plus de réponses référez-vous ici Appeler des méthodes sur des composants enfants React

En consultant les refs du composant "reason", vous brisez l'encapsulation et rendez impossible la refactorisation de ce composant sans examiner soigneusement tous les endroits où il est utilisé. Pour cette raison, nous recommandons fortement de traiter les refs comme des éléments privés d'un composant, tout comme l'état.

En général, les données doivent être transmises dans l'arbre via des props. Il existe quelques exceptions à cette règle (comme l'appel de .focus() ou le déclenchement d'une animation unique qui ne "change" pas vraiment l'état), mais chaque fois que vous exposez une méthode appelée "set", les props constituent généralement un meilleur choix. Essayez de faire en sorte que le composant d'entrée interne se préoccupe de sa taille et de son apparence afin qu'aucun de ses ancêtres ne le fasse.

8 votes

Voici la source de cette réponse : discuss.reactjs.org/t/ . Je n'ai aucun problème à citer d'autres personnes, mais mettez au moins une référence.

7 votes

En quoi cela casse-t-il l'encapsulation plus que les accessoires ?

9voto

joeytwiddle Points 3226

Si vous faites cela simplement parce que vous voulez que l'enfant fournisse un trait réutilisable à ses parents, alors vous pouvez envisager de faire ceci utiliser les render-props à la place.

Cette technique permet en fait de mettre la structure sens dessus dessous. Le site Child enveloppe maintenant le parent, donc je l'ai renommé en AlertTrait ci-dessous. J'ai gardé le nom Parent pour la continuité, bien que ce ne soit pas vraiment un parent maintenant.

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

Dans ce cas, le trait d'alerte fournit un ou plusieurs traits qu'il transmet en tant que props à la composante qui lui a été donnée dans son module renderComponent prop.

Le parent reçoit doAlert comme un accessoire, et peut l'appeler en cas de besoin.

(Pour plus de clarté, j'ai appelé le prop renderComponent dans l'exemple ci-dessus. Mais dans les documents de React liés ci-dessus, ils l'appellent simplement render .)

Le composant Trait peut rendre les éléments entourant le parent, dans sa fonction de rendu, mais il ne rend rien à l'intérieur du parent. En fait, il pourrait rendre des choses à l'intérieur du parent, s'il passait une autre prop (par exemple, la fonction de rendu du composant Trait). renderChild ) au parent, que ce dernier peut ensuite utiliser pendant sa méthode de rendu.

C'est un peu différent de ce que le PO a demandé, mais certaines personnes peuvent se retrouver ici (comme nous l'avons fait) parce qu'elles voulaient créer un trait réutilisable, et ont pensé qu'un composant enfant était un bon moyen de le faire.

0 votes

Vous trouverez ici une liste pratique de modèles permettant de créer des traits réutilisables : reactjs.org/blog/2016/07/13/

0 votes

Que se passe-t-il si vous avez N chronomètres et un bouton pour les redémarrer tous. Comment rendre les accessoires pratiques ici ?

0 votes

@vsync Je ne suis pas sûr que cette méthode puisse vous aider dans votre tâche. Mais La réponse de brickingup pourrait vous aider. Notez qu'ils fixent this.clickChild = click mais vos multiples chronomètres passeraient plusieurs fonctions, il faudrait donc les stocker toutes : this.watchRestartFuncs[watchId] = restartWatch

1voto

szpada87 Points 1

Vous pouvez faire de l'inversion d'héritage (voir ici) : https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e ). De cette façon, vous avez accès à l'instance du composant que vous allez envelopper (vous pourrez donc accéder à ses fonctions).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X