web-dev-qa-db-fra.com

Transférer les accessoires au composant parent dans React.js

N'y a-t-il pas un moyen simple de passer le props d'un enfant à son parent à l'aide d'é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 transmettre la valeur d'une entrée, mais il serait agréable de passer l'ensemble du kit n 'kaboodle. Parfois, le composant enfant contient un ensemble d'informations que vous préférez ne pas rechercher.

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

MISE À JOUR - 01/09/2015

Après avoir utilisé React pendant plus d'un an, et sous l'impulsion de la réponse de Sebastien Lorber, j'ai conclu que le fait de passer des composants enfants comme arguments à des fonctions chez les parents est pas en fait, le React manière, ce n'était pas non plus une bonne idée. J'ai changé la réponse.

263
KendallB

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

Cette réponse traite simplement le cas de relation directe parent-enfant. Lorsque le parent et l’enfant ont potentiellement beaucoup d’intermédiaires, vérifiez ceci réponse .

D'autres solutions manquent le point

Bien qu'elles fonctionnent toujours bien, il manque quelque chose de très important dans les autres réponses.

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

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

Meilleure mise en œuvre

Enfant : ça n'a vraiment pas à être plus compliqué que ça.

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

Parent avec un seul enfant : utilisation de la valeur transmise à 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 d'enfants : vous avez toujours tout ce dont vous avez besoin sur le parent et vous 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 d'utiliser this.state.childrenData[childIndex]

Notez que nous lions avec un contexte null car sinon React envoie un avertissement relatif à son système auto-reliure . Utiliser null signifie que vous ne voulez pas changer le contexte de la fonction. Voir aussi .

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

C'est pour moi une mauvaise idée en terme de couplage 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 d'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 référence directement sur l'enfant:

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

Et accédez au nœud DOM dans le parent avec

React.findDOMNode(this.refs.theChild)

Pour les cas plus avancés où vous souhaitez accéder à plusieurs références de l'enfant dans le parent, celui-ci peut transmettre tous les nœuds dom directement dans le rappel.

Le composant a une interface (props) et le parent ne doit rien présumer du fonctionnement interne de l'enfant, y compris de sa structure DOM interne ou des noeuds DOM pour lesquels il déclare des références. Un parent utilisant le ref d'un enfant signifie que vous couplez étroitement les 2 composants.

Pour illustrer le problème, je prendrai cette citation sur le Shadow DOM , qui est utilisé dans les navigateurs pour rendre des éléments tels que des curseurs, des barres de défilement, des lecteurs vidéo ...:

Ils ont créé une frontière entre ce que vous, le développeur Web, pouvez atteindre et ce que l’on considère comme des détails d’implémentation, donc inaccessibles pour vous. Le navigateur peut toutefois traverser cette limite à sa guise. Avec cette limite en place, ils ont pu créer tous les éléments HTML en utilisant les mêmes technologies anciennes, en dehors des divs et des span, comme vous le feriez.

Le problème est que si vous laissez les détails d'implémentation de l'enfant s'infiltrer dans le parent, il devient 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), cela est très dangereux car vous laissez le client trop accéder, 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 noeuds 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 effectue sa mise à jour automatique après la refactorisation 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.

À propos d'utiliser autre chose

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


Edit: exemples ES6

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 sous forme d'accessoire 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 cela peut aussi ê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é par défaut parce que vous utilisez onClick={e => doSomething()}, ou une liaison directe pendant la phase de rendu, car il créera une nouvelle fonction à chaque fois que le parent restituera. S'il s'agit d'un goulot d'étranglement dans votre application, vous pouvez transmettre les données aux enfants et les réinjecter dans un rappel "stable" (défini sur la classe parente et lié à this dans le constructeur de la classe) de sorte que PureComponent optimisation peut entrer en jeu ou vous pouvez implémenter votre propre shouldComponentUpdate et ignorer le rappel dans le contrôle de comparaison des accessoires.

Vous pouvez également utiliser la bibliothèque Recompose , qui fournit des composants d'ordre supérieur pour obtenir des optimisations plus fines:

// 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)
243
Sebastien Lorber

Mise à jour (01/09/15): Le PO a fait de cette question un peu une cible en mouvement. 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 la variable onClick de Child en 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 de la manière suivante:

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

instantané JSBin


Mais la question elle-même est trompeuse

Le parent connaît déjà props de l’enfant.

Cela n’est pas clair dans l’exemple fourni car aucun accessoire n’est fourni. Cet exemple de code pourrait mieux prendre en charge 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à quels sont les accessoires de Child.

instantané JSBin


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

S'il est vraiment question d'utiliser les accessoires d'un enfant, vous pouvez éviter toute relation avec Child.

JSX a un propagation des attributs API 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} />;
  }
});

instantané JSBin


Et aucune configuration supplémentaire n'est requise lorsque vous raccordez 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>;
  }
});

instantané JSBin

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


Un exemple pratique robuste

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

exemple de travail DTServiceCalculator
repo DTServiceCalculator

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

Les enfants sont parfaitement ignorants

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

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

Il ne fait qu'appeler le callback handleClick fourni avec le index [ source ] fourni.

Les parents sont des enfants

DTServicesCalculator le composant parent est cet exemple. C’est aussi un enfant. Regardons.

DTServiceCalculator crée une liste de composants enfants (ServiceItems) et leur fournit les accessoires [ source ]. C’est le composant parent de ServiceItem, mais c’est le composant enfant du composant qui lui transmet la liste. Il ne possède pas les données. Donc, il délègue à nouveau le traitement 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 obtenions le composant qui possède l’état.

Lorsque nous y arrivons enfin, nous gérons la sélection/désélection d’état comme suit: [ 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 extérieurs aussi opaques que possible. S'efforcer de s'assurer qu'ils ont très peu de préférences sur la manière dont un composant parent peut choisir de les implémenter.

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

De plus: le modèle de flux est un bon moyen de réduire ce type de raccordement nécessaire dans les applications.

141
chantastic

Il semble y avoir une réponse simple. Considère 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 touche 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 des mondes.

MISE À JOUR: 01/09/2015

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

24
KendallB

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

//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

8
Roman

En gros, vous utilisez des accessoires pour envoyer des informations à et de Child et Parent.

Ajoutant à toutes les merveilleuses réponses, laissez-moi vous donner un exemple simple qui explique comment passer des valeurs d’un composant enfant à un 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>
      );
   }
}

Header.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>

      );
   }
}

Main.js

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

import App from './App.jsx';

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

Ça y est, vous pouvez maintenant transmettre les valeurs de votre client au serveur.

Jetez un coup d'œil à la fonction Change dans Header.js

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

C’est ainsi que vous insérez des valeurs dans les accessoires du client au serveur.

6
Ignatius Andrew

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 dont le tutoriel de réaction officiel le recommande (il existe également une syntaxe des champs de classe publique qui n’est pas traitée ici). Vous pouvez trouver toutes ces informations ici https://reactjs.org/docs/handling-events.html

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

  1. Assurez-vous dans le constructeur parent que vous liez la fonction que vous avez créée dans le parent
  2. Transmettez la fonction liée à l'enfant en tant qu'accessoire (pas de lambda car nous passons un 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 au chargement et ne sera pas déclenchée sur l'événement.)

Fonction parent

handleFilterApply(filterVals){} 

Constructeur Parent

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

Prop Passed to Child

onApplyClick = {this.handleFilterApply}

Appel événement enfant

onClick = {() => {props.onApplyClick(filterVals)}
1
Jordan H