Passez les accessoires au composant parent dans React.js

N'y a-t-il pas un moyen simple de passer props d'un enfant à son parent en utilisant des événements, dans React.js?

var Child = React.createClass({
  render: function() {
    <a onClick={this.props.onClick}>Click me</a>
  }
});

var Parent = React.createClass({
  onClick: function(event) {
    // event.component.props ?why is this not available?
  },
  render: function() {
    <Child onClick={this.onClick} />
  }
});

Je sais que vous pouvez utiliser des composants contrôlés pour passer la valeur d'une entrée, mais ce serait bien de passer tout le kit n' kaboodle. Parfois, le composant enfant contient un ensemble d'informations que vous préférez ne pas avoir à rechercher.

Peut-être y a-t-il un moyen de lier le composant à l'événement?

Mise à jour-9/1/2015

Après avoir utilisé React pendant plus d'un an, et stimulé par La réponse de Sébastien Lorber, j'ai conclu que passer des composants enfants comme arguments aux fonctions chez les parents n'est pas {[12] } en fait la manière de réagir, et ce n'était jamais une bonne idée. J'ai passé de la réponse.

230
demandé sur KendallB 2014-03-25 19:44:44

6 réponses

Edit : voir les exemples de fin pour les exemples mis à jour ES6.

Cette réponse traite simplement le cas de la relation parent-enfant directe. Lorsque le parent et l'enfant ont potentiellement beaucoup d'intermédiaires, cochez cette réponse.

D'autres solutions manquent le point

Bien qu'ils fonctionnent toujours bien, d'autres réponses manquent quelque chose de très important.

N'y a-t-il pas un moyen simple de transmettre les accessoires d'un enfant à son parent en utilisant des événements, à Réagir.js?

Le parent a déjà cet accessoire enfant!: si l'enfant a un accessoire, alors c'est parce que son parent a fourni cet accessoire à l'enfant! Pourquoi voulez-vous que l'enfant passe l'accessoire au parent, alors que le parent a évidemment déjà cet accessoire?

Meilleure mise en œuvre

Enfant: il n'a pas vraiment plus compliqué que cela.

var Child = React.createClass({
  render: function () {
    return <button onClick={this.props.onClick}>{this.props.text}</button>;
  },
});

Parent avec un seul enfant : en utilisant la valeur à laquelle il passe l'enfant

var Parent = React.createClass({
  getInitialState: function() {
     return {childText: "Click me! (parent prop)"};
  },
  render: function () {
    return (
      <Child onClick={this.handleChildClick} text={this.state.childText}/>
    );
  },
  handleChildClick: function(event) {
     // You can access the prop you pass to the children 
     // because you already have it! 
     // Here you have it in state but it could also be
     //  in props, coming from another parent.
     alert("The Child button text is: " + this.state.childText);
     // You can also access the target of the click here 
     // if you want to do some magic stuff
     alert("The Child HTML is: " + event.target.outerHTML);
  }
});

JsFiddle

Parent avec liste des enfants : Vous avez toujours tout ce dont vous avez besoin sur le parent et n'avez pas besoin de rendre l'enfant plus compliqué.

var Parent = React.createClass({
  getInitialState: function() {
     return {childrenData: [
         {childText: "Click me 1!", childNumber: 1},
         {childText: "Click me 2!", childNumber: 2}
     ]};
  },
  render: function () {
    var children = this.state.childrenData.map(function(childData,childIndex) {
        return <Child onClick={this.handleChildClick.bind(null,childData)} text={childData.childText}/>;
    }.bind(this));
    return <div>{children}</div>;
  },

  handleChildClick: function(childData,event) {
     alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
     alert("The Child HTML is: " + event.target.outerHTML);
  }
});

JsFiddle

, Il est également possible d'utiliser this.handleChildClick.bind(null,childIndex), puis utiliser this.state.childrenData[childIndex]

Notez que nous nous lions avec un contexte null car sinon React émet un avertissement lié à son systèmeautobinding . L'utilisation de null signifie que vous ne voulez pas changer la fonction de cadre. Voir aussi.

À propos de l'encapsulation et du couplage dans d'autres réponses

C'est pour moi un mauvaise idée en terme d'assemblage et d'encapsulation:

var Parent = React.createClass({
  handleClick: function(childComponent) {
     // using childComponent.props
     // using childComponent.refs.button
     // or anything else using childComponent
  },
  render: function() {
    <Child onClick={this.handleClick} />
  }
});

Utilisation des accessoires : Comme je l'ai expliqué ci-dessus, vous avez déjà les accessoires dans le parent, il est donc inutile de passer tout le composant enfant pour accéder aux accessoires.

Utilisation de refs : Vous avez déjà la cible de clic dans l'événement, et dans la plupart des cas, cela suffit. De plus, vous auriez pu utiliser une ref directement sur l'enfant:

<Child ref="theChild" .../>

Et accéder au noeud DOM dans le parent avec

React.findDOMNode(this.refs.theChild)

Pour les cas plus avancés où vous souhaitez accéder à plusieurs refs de l'enfant dans le parent, l'enfant peut passer tous les nœuds dom directement dans le rappel.

Le composant a une interface (props) et le parent ne doit rien assumer sur le fonctionnement interne de l'enfant, y compris sa structure DOM interne ou quels nœuds DOM il déclare refs pour. Un parent utilisant une référence d'un enfant signifie que vous couplez étroitement les 2 composants.

Pour illustrer le problème, je vais prendre cette citation sur le Shadow DOM , qui est utilisé dans les navigateurs pour rendre des choses comme les curseurs, les barres de défilement, les lecteurs vidéo...:

Ils ont créé une limite entre ce que vous, le développeur Web peut atteindre et ce qui est considéré comme des détails de mise en œuvre, donc inaccessible à vous. Le navigateur cependant, peut traipse à travers cela limite à volonté. Avec cette limite en place, ils ont pu construire tous les éléments HTML en utilisant les mêmes bonnes vieilles technologies Web, hors des divs et des spans tout comme vous.

Le problème est que si vous laissez les détails de l'implémentation enfant s'infiltrer dans le parent, il est très difficile de refactoriser l'enfant sans affecter le parent. Cela signifie qu'en tant qu'auteur de bibliothèque (ou en tant qu'éditeur de navigateur avec Shadow DOM) c'est très dangereux car vous laissez le client accéder aussi beaucoup, ce qui rend très difficile la mise à niveau du code sans casser la rétrocompatibilité.

Si Chrome avait implémenté sa barre de défilement en laissant le client accéder aux nœuds dom internes de cette barre de défilement, cela signifie que le client peut avoir la possibilité de simplement casser cette barre de défilement, et que les applications se casseraient plus facilement lorsque Chrome effectuerait sa mise à jour automatique après refactoring de la barre de défilement... Au lieu de cela, ils ne donnent accès qu'à certaines choses sûres comme la personnalisation de certaines parties de la barre de défilement avec CSS.

A propos d'utiliser autre chose

Passer le composant entier dans le rappel est dangereux et peut conduire les développeurs novices à faire des choses très étranges comme appeler childComponent.setState(...) ou childComponent.forceUpdate(), ou lui attribuer de nouvelles variables, à l'intérieur du parent, rendant l'application entière beaucoup plus difficile à raisonner.


Edit: ES6 exemples

Comme beaucoup de gens utilisent maintenant ES6, voici les mêmes exemples pour la syntaxe ES6

, L'enfant peut être très simple:

const Child = ({
  onClick, 
  text
}) => (
  <button onClick={onClick}>
    {text}
  </button>
)

Le parent peut être soit une classe (et il peut éventuellement gérer l'État lui-même, mais je le passe comme accessoires ici:

class Parent1 extends React.Component {
  handleChildClick(childData,event) {
     alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
     alert("The Child HTML is: " + event.target.outerHTML);
  }
  render() {
    return (
      <div>
        {this.props.childrenData.map(child => (
          <Child
            key={child.childNumber}
            text={child.childText} 
            onClick={e => this.handleChildClick(child,e)}
          />
        ))}
      </div>
    );
  }
}

Mais il peut également être simplifié s'il n'a pas besoin de gérer l'État:

const Parent2 = ({childrenData}) => (
  <div>
     {childrenData.map(child => (
       <Child
         key={child.childNumber}
         text={child.childText} 
         onClick={e => {
            alert("The Child button data is: " + child.childText + " - " + child.childNumber);
                    alert("The Child HTML is: " + e.target.outerHTML);
         }}
       />
     ))}
  </div>
)

JsFiddle


Avertissement PERF (s'applique à ES5 / ES6): si vous utilisez PureComponent ou shouldComponentUpdate, les implémentations ci-dessus ne seront pas optimisées par défaut car vous utilisez onClick={e => doSomething()}, ou vous liez directement pendant la phase de rendu, parce qu'il va créer une nouvelle fonction chaque fois que le parent rend. S'il s'agit d'un goulot d'étranglement dans votre application, vous pouvez passer les données aux enfants et les réinjecter dans le rappel "stable" (défini sur la classe parent et lié à this dans le constructeur de classe) pour que l'optimisation PureComponent puisse démarrer, ou vous pouvez implémenter votre propre shouldComponentUpdate et ignorer le rappel dans la vérification de

Vous pouvez également utiliser la bibliothèque Recompose , qui fournit des composants d'ordre supérieur à réaliser des optimisations affinées:

// A component that is expensive to render
const ExpensiveComponent = ({ propA, propB }) => {...}

// Optimized version of same component, using shallow comparison of props
// Same effect as React's PureRenderMixin
const OptimizedComponent = pure(ExpensiveComponent)

// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedComponent = onlyUpdateForKeys(['propA', 'propB'])(ExpensiveComponent)

Dans ce cas, vous pouvez optimiser le composant enfant en utilisant:

const OptimizedChild = onlyUpdateForKeys(['text'])(Child)
218
répondu Sebastien Lorber 2017-05-23 11:55:01

mise à jour (9/1/15): le PO a fait de cette question une cible mouvante. Il a été mis à jour à nouveau. Donc, je me sens responsable de mettre à jour ma réponse.

Tout d'abord, une réponse à votre exemple fourni:

Oui, c'est possible.

Vous pouvez résoudre ce problème en mettant à jour onClick de L'enfant pour qu'il soit this.props.onClick.bind(null, this):

var Child = React.createClass({
  render: function () {
    return <a onClick={this.props.onClick.bind(null, this)}>Click me</a>;
  }
});

Le gestionnaire d'événements de votre Parent peut alors accéder au composant et à l'événement comme suit:

  onClick: function (component, event) {
    // console.log(component, event);
  },

JSBin instantané


Mais la question elle-même est trompeuse

Le Parent connaît déjà props de L'enfant.

Ce n'est pas clair dans l'exemple fourni car aucun accessoire n'est réellement fourni. Cet exemple de code pourrait mieux supporter la question posée:

var Child = React.createClass({
  render: function () {
    return <a onClick={this.props.onClick}> {this.props.text} </a>;
  }
});

var Parent = React.createClass({
  getInitialState: function () {
    return { text: "Click here" };
  },
  onClick: function (event) {
    // event.component.props ?why is this not available? 
  },
  render: function() {
    return <Child onClick={this.onClick} text={this.state.text} />;
  }
});

Il devient beaucoup plus clair dans cet exemple que vous savez déjà ce que les accessoires de l'Enfant.

JSBin instantané


S'il s'agit vraiment d'utiliser les accessoires D'un enfant...

S'il s'agit vraiment d'utiliser les accessoires D'un enfant, vous pouvez éviter tout branchement avec L'enfant tout à fait.

JSX a une APIspread attributes que j'utilise souvent sur des composants comme Child. Il prend tous les props et les applique à un composant. Enfant ressemblerait à ceci:

var Child = React.createClass({
  render: function () {
    return <a {...this.props}> {this.props.text} </a>;
  }
});

Vous permettant d'utiliser les valeurs directement dans le Parent:

var Parent = React.createClass({
  getInitialState: function () {
    return { text: "Click here" };
  },
  onClick: function (text) {
    alert(text);
  },
  render: function() {
    return <Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />;
  }
});

JSBin instantané


Et il n " y a pas de configuration supplémentaire requise que vous brancher des composants enfants supplémentaires

var Parent = React.createClass({
  getInitialState: function () {
    return {
      text: "Click here",
      text2: "No, Click here",
    };
  },
  onClick: function (text) {
    alert(text);
  },
  render: function() {
    return <div>
      <Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />
      <Child onClick={this.onClick.bind(null, this.state.text2)} text={this.state.text2} />
    </div>;
  }
});

JSBin instantané

, Mais je soupçonne que ce n'est pas votre cas d'utilisation. Alors creusons plus loin ...


Un exemple pratique robuste

La nature générique de l'exemple fourni est difficile à parler. J'ai créé un composant qui démontre une utilisation pratique pour la question ci dessus, implémenté de manière très Reacty :

DTServiceCalculator exemple de travail
DTServiceCalculator repo

Ce composant est un calculateur de service simple. Vous lui fournissez une liste de services (avec les noms et les prix) et il calculera un total des Prix sélectionnés.

Les enfants sont parfaitement ignorants

ServiceItem est l'enfant du composant dans cet exemple. Il n'a pas beaucoup d'opinions sur le monde extérieur. Il nécessite quelques accessoires , dont l'un est une fonction à appeler lorsque vous cliquez sur.

<div onClick={this.props.handleClick.bind(this.props.index)} />

, Il ne fait rien, mais pour appeler la condition handleClick rappel avec le index[source].

Les Parents sont des enfants

DTServicesCalculator est le parent-composante est de cet exemple. C'est aussi un enfant. Regardons.

DTServiceCalculator crée une liste de l'enfant composant (ServiceItems) et leur offre avec des accessoires [source]. C'est l' parent-component de ServiceItem mais c'est le composant enfant du composant qui lui transmet la liste. Il n'est pas propriétaire des données. Donc, il délègue à nouveau la gestion du composant à son composant parent source

<ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} />

handleServiceItem capture l'index, transmis par l'enfant, et le fournit à son parent [source ]

handleServiceClick (index) {
  this.props.onSelect(index);
}

Les propriétaires savent tout

Le concept de "propriété" est important dans React. Je recommande de lire plus à ce sujet ici.

Dans l'exemple que j'ai montré, je continue à déléguer la gestion d'un événement dans l'arborescence des composants jusqu'à ce que nous arrivions au composant propriétaire de l'état.

Quand nous y arrivons enfin, nous gérons la sélection/désélection d'état comme ceci [source]:

handleSelect (index) {
  let services = […this.state.services];
  services[index].chosen = (services[index].chosen) ? false : true;
  this.setState({ services: services });
}


Conclusion

Essayez de garder vos composants les plus externes aussi opaques que possible. Efforcez-vous de vous assurer qu'ils ont très peu de préférences sur la façon dont un composant parent pouvez choisir de les mettre en œuvre.

Tenez compte de qui possède les données que vous manipulez. Dans la plupart des cas, vous devrez déléguer la gestion des événements à l'arborescence au composant propriétaire de cet état.

mis à part: le flux pattern est un bon moyen de réduire ce type de branchement nécessaire dans les applications.

140
répondu chantastic 2017-09-13 11:08:40

Il semble qu'il y ait une réponse simple. Considérez ceci:

var Child = React.createClass({
  render: function() {
    <a onClick={this.props.onClick.bind(null, this)}>Click me</a>
  }
});

var Parent = React.createClass({
  onClick: function(component, event) {
    component.props // #=> {Object...}
  },
  render: function() {
    <Child onClick={this.onClick} />
  }
});

La clé appelle bind(null, this) sur l'événement this.props.onClick, transmis par le parent. Maintenant, la fonction onClick accepte les arguments component et event. Je pense que c'est le meilleur de tous les mondes.

Mise à jour: 9/1/2015

C'était une mauvaise idée: laisser les détails de l'implémentation de l'enfant s'infiltrer dans le parent n'a jamais été un bon chemin. Voir la réponse de Sébastien Lorber.

24
répondu KendallB 2015-08-01 15:09:27

La question Est de savoir comment passer l'argument de l'enfant au composant parent. Cet exemple est facile à utiliser et testé:

//Child component
class Child extends React.Component {
    render() {
        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div><button onClick={() => handleToUpdate('someVar')}>Push me</button></div>
        )
    }
}

//Parent component
class Parent extends React.Component {
        constructor(props) {
        super(props);
        var handleToUpdate  = this.handleToUpdate.bind(this);
    }

        handleToUpdate(someArg){
            alert('We pass argument from Child to Parent: \n' + someArg);
    }

    render() {
        var handleToUpdate  =   this.handleToUpdate;
        return (<div>
                    <Child handleToUpdate = {handleToUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <Parent />,
        document.querySelector("#demo")
    );
}

Regardez JSFIDDLE

7
répondu Roman 2017-03-02 08:09:41

Fondamentalement, vous utilisez des accessoires pour envoyer des informations à et à partir de L'enfant et du Parent.

En ajoutant à toutes les réponses merveilleuses, permettez-moi de donner un exemple simple qui explique le passage de valeurs de l'enfant au composant parent dans React

App.js

class App extends React.Component {
      constructor(){
            super();
            this.handleFilterUpdate = this.handleFilterUpdate.bind(this);
            this.state={name:'igi'}
      }
      handleFilterUpdate(filterValue) {
            this.setState({
                  name: filterValue
            });
      }
   render() {
      return (
        <div>
            <Header change={this.handleFilterUpdate} name={this.state.name} />
            <p>{this.state.name}</p>
        </div>
      );
   }
}

En-tête.js

class Header extends React.Component {
      constructor(){
            super();
            this.state={
                  names: 'jessy'
            }
      }
      Change(event) {

      // this.props.change(this.state.names);
      this.props.change('jessy');
  }

   render() {
      return (
       <button onClick={this.Change.bind(this)}>click</button>

      );
   }
}

Principal.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App.jsx';

ReactDOM.render(<App />, document.getElementById('app'));

C'est tout , maintenant vous pouvez passer des valeurs de votre client au serveur.

Jetez un oeil à la fonction de changement dans le Tête.js

Change(event) {
      // this.props.change(this.state.names);
      this.props.change('jessy');
  }

C'est ainsi que vous poussez les valeurs dans les accessoires du client vers le serveur

5
répondu Ignatius Andrew 2016-11-01 12:00:47

Voici une simple implémentation ES6 en 3 étapes utilisant la liaison de fonction dans le constructeur parent. C'est la première façon que le tutoriel officiel react recommande (il y a aussi la syntaxe des champs de classe publique qui n'est pas couverte ici). Vous pouvez trouver toutes ces informations ici https://reactjs.org/docs/handling-events.html

Liaison des fonctions parentes afin que les enfants puissent les appeler (et transmettre des données au parent! : D)

  1. assurez-vous que dans le constructeur parent vous liez la fonction vous avez créé dans le parent
  2. passez la fonction liée à l'enfant en tant qu'accessoire (pas de lambda parce que nous passons une ref à la fonction)
  3. appelez la fonction liée à partir d'un événement enfant (Lambda! Nous appelons la fonction lorsque l'événement est déclenché. Si nous ne le faisons pas, la fonction s'exécutera automatiquement en charge et ne sera pas déclenchée sur l'événement.)

Fonction Parent

handleFilterApply(filterVals){} 

Constructeur Parent

this.handleFilterApply = this.handleFilterApply.bind(this);

Prop passé à Enfant

onApplyClick = {this.handleFilterApply}

Appel D'Événement Enfant

onClick = {() => {props.onApplyClick(filterVals)}
1
répondu Jordan H 2017-11-09 20:48:33