Passar adereços para o componente pai no React.js

295

Não existe uma maneira simples de transmitir uma criança propsao pai usando eventos, no 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} />
  }
});

Eu sei que você pode usar componentes controlados para passar o valor de uma entrada, mas seria bom passar o kit todo no kaboodle. Às vezes, o componente filho contém um conjunto de informações que você prefere não procurar.

Talvez haja uma maneira de vincular o componente ao evento?

ATUALIZAÇÃO - 1/9/2015

Depois de usar o React por mais de um ano, e estimulado pela resposta de Sebastien Lorber, concluí que a transmissão de componentes filhos como argumentos para funções nos pais não é , de fato, o modo React, nem nunca foi uma boa idéia. Eu mudei a resposta.

KendallB
fonte
Existem várias respostas para problemas semelhantes ( aqui e aqui ), mas nenhum deles parece particularmente elegante
Bojangles
Eu concordo - passar eventos pela cadeia é ótimo, mas seria incrível saber com certeza qual componente originou o evento.
KendallB
Por favor, dê uma olhada nas minhas respostas, pois acho que a resposta aceita não é boa o suficiente; stackoverflow.com/a/31756470/82609 #
Sebastien Lorber
1
kit n 'kaboodle - Para incluir tudo. Para pegar todos os sinos e assobios. A Combonação. urbandictionary.com/define.php?term=kit%20and%20caboodle
Filip Bartuzi

Respostas:

268

Editar : veja os exemplos finais para ver os exemplos atualizados do ES6.

Essa resposta simplesmente trata do caso de relacionamento direto pai-filho. Quando pai e filho têm potencialmente muitos intermediários, verifique esta resposta .

Outras soluções estão faltando

Enquanto eles ainda funcionam bem, outras respostas estão faltando algo muito importante.

Não existe uma maneira simples de passar os acessórios de uma criança para o pai usando eventos, no React.js?

Os pais já têm esse suporte filho! : se a criança tem um suporte, é porque seus pais forneceram esse suporte à criança! Por que você quer que a criança devolva o suporte aos pais, enquanto os pais obviamente já têm esse suporte?

Melhor implementação

Criança : realmente não precisa ser mais complicado que isso.

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

Pai com filho único : usando o valor que ele passa para o filho

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

Pai com lista de filhos : você ainda tem tudo o que precisa no pai e não precisa tornar a criança mais complicada.

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

Também é possível usar this.handleChildClick.bind(null,childIndex)e depois usarthis.state.childrenData[childIndex]

Observe que estamos vinculando um nullcontexto porque, caso contrário, o React emite um aviso relacionado ao seu sistema de vinculação automática. Usar nulo significa que você não deseja alterar o contexto da função. Veja também .

Sobre encapsulamento e acoplamento em outras respostas

Isso é para mim uma péssima idéia em termos de acoplamento e encapsulamento:

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

Usando adereços : Como expliquei acima, você já possui os adereços no pai, portanto é inútil passar todo o componente filho para acessar os adereços.

Usando refs : você já tem o destino do clique no evento e, na maioria dos casos, isso é suficiente. Além disso, você poderia ter usado um árbitro diretamente na criança:

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

E acesse o nó DOM no pai com

React.findDOMNode(this.refs.theChild)

Para casos mais avançados em que você deseja acessar várias referências do filho no pai, o filho pode passar todos os nós de domínio diretamente no retorno de chamada.

O componente tem uma interface (props) e o pai não deve assumir nada sobre o trabalho interno do filho, incluindo sua estrutura DOM interna ou para quais nós DOM ele declara refs. Um pai que usa uma referência de um filho significa que você acopla firmemente os 2 componentes.

Para ilustrar o problema, levarei esta citação sobre o Shadow DOM , que é usado dentro dos navegadores para renderizar coisas como controles deslizantes, barras de rolagem, players de vídeo ...:

Eles criaram um limite entre o que você, o desenvolvedor da Web pode alcançar, e o que é considerado detalhes de implementação, inacessíveis a você. O navegador, no entanto, pode atravessar esse limite à vontade. Com esse limite, eles foram capazes de criar todos os elementos HTML usando as mesmas boas e antigas tecnologias da Web, a partir dos divs e dos intervalos, como você faria.

O problema é que, se você deixar os detalhes da implementação filho vazarem para o pai, dificulta a refatoração do filho sem afetar o pai. Isso significa que, como autor da biblioteca (ou como editor do navegador com o Shadow DOM), isso é muito perigoso, porque você deixa o cliente acessar demais, dificultando a atualização do código sem interromper a retrocompatibilidade.

Se o Chrome implementou sua barra de rolagem, permitindo que o cliente acesse os nós de domínio internos dessa barra de rolagem, isso significa que o cliente pode ter a possibilidade de simplesmente quebrar essa barra de rolagem e que os aplicativos quebrariam mais facilmente quando o Chrome executasse sua atualização automática após refatorar o barra de rolagem ... Em vez disso, eles apenas dão acesso a algumas coisas seguras, como personalizar algumas partes da barra de rolagem com CSS.

Sobre como usar qualquer outra coisa

Passando toda a componente no callback é perigoso e pode levar desenvolvedores novatos para fazer coisas muito estranhas como a chamada childComponent.setState(...)ou childComponent.forceUpdate(), ou atribuindo-lhe novas variáveis, dentro do pai, fazendo com que o aplicativo inteiro muito mais difícil de raciocinar sobre.


Edit: ES6 examples

Como muitas pessoas agora usam o ES6, eis os mesmos exemplos de sintaxe do ES6

A criança pode ser muito simples:

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

O pai pode ser uma classe (e, eventualmente, pode gerenciar o próprio estado, mas estou passando isso como adereços aqui:

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

Mas também pode ser simplificado se não for necessário gerenciar o estado:

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


AVISO PERF (aplicável ao ES5 / ES6): se você estiver usando PureComponentou shouldComponentUpdate, as implementações acima não serão otimizadas por padrão porque usam onClick={e => doSomething()}ou vinculam diretamente durante a fase de renderização, porque criarão uma nova função sempre que o pai for renderizado. Se esse é um gargalo perfeito no seu aplicativo, você pode passar os dados para os filhos e reiniciá-los no retorno de chamada "estável" (definido na classe pai e vinculado ao thisconstrutor da classe) para que a PureComponentotimização possa ser ativada ou você pode implementar o seu próprio shouldComponentUpdatee ignorar o retorno de chamada na verificação de comparação de adereços.

Você também pode usar a biblioteca Recompose , que fornece componentes de ordem superior para obter otimizações aprimoradas:

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

Nesse caso, você pode otimizar o componente filho usando:

const OptimizedChild = onlyUpdateForKeys(['text'])(Child)
Sebastien Lorber
fonte
1
Sim, tendo passado o último ano usando o React, eu concordo - não há uma boa razão para passar componentes instanciados por meio de funções. Vou depreciar minha própria resposta. Também estou disposto a mudar a resposta, talvez com algumas modificações: sua "Melhor implementação" não aborda o motivo real pela qual essa pergunta surgiu, ou seja, "Como sei qual dos meus filhos foi escolhido entre um grupo?" O fluxo é a resposta para qualquer aplicativo de tamanho decente, no entanto, em aplicativos pequenos, é bom passar valores (não componentes) para fazer backup da cadeia de chamadas por meio de uma função ativada props. Aceita?
KendallB
@ KendallB feliz por concordar com a minha resposta :) How do I know which of my children was chosen among a group?isso realmente não faz parte da pergunta original, mas incluí a solução na minha resposta. Ao contrário da sua edição, acho que não é necessário modificar o filho, mesmo ao lidar com uma lista, pois você pode usá-lo binddiretamente no pai.
Sebastien Lorber
Fiz uma edição na sua resposta, a parte sobre referências. omerwazir.com/posts/react-getdomnode-replaced-with-findDOMNode
ffxsam
Oi @SebastienLorber Estou vindo do futuro. Em relação a esta linha que onClick={this.handleChildClick.bind(null,childData)}você mencionou sobre a ligação automática Isso ainda é aplicável agora que o React não foi incluído no modelo de classe . Eu li muitas coisas, mas essa parte ainda me confunde. A class modelpropósito, estou usando o ES6 , então mudei nullpara thise meu código ainda parece funcionar. Como você traduziria essa parte do seu código em um estilo ES6?
JohnnyQ
Também estou lutando para que isso funcione no mais recente React, o que precisa mudar?
Hussein Duvigneau
143

Atualização (1/9/15): O OP transformou essa questão em um alvo em movimento. Foi atualizado novamente. Portanto, sinto-me responsável por atualizar minha resposta.

Primeiro, uma resposta para o seu exemplo fornecido:

Sim, isso é possível.

Você pode resolver isso atualizando Child's onClickcomo this.props.onClick.bind(null, this):

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

O manipulador de eventos em seu pai pode acessar o componente e o evento da seguinte forma:

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

Instantâneo JSBin


Mas a pergunta em si é enganosa

O pai já conhece o filho props.

Isso não está claro no exemplo fornecido, porque na verdade não há props. Esse código de exemplo pode suportar melhor a pergunta que está sendo feita:

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

Torna-se muito mais claro neste exemplo que você já sabe quais são os objetos de criança.

Instantâneo JSBin


Se é realmente sobre o uso de adereços de uma criança ...

Se realmente se trata de usar adereços de uma criança, você pode evitar qualquer conexão com a criança completamente.

O JSX possui uma API de atributos de propagação que costumo usar em componentes como Child. Ele pega todos os propse os aplica a um componente. Criança ficaria assim:

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

Permitindo que você use os valores diretamente no Pai:

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

Instantâneo JSBin


E não é necessária nenhuma configuração adicional à medida que você conecta componentes filho adicionais

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

Instantâneo JSBin

Mas suspeito que esse não seja seu caso de uso real. Então vamos cavar mais ...


Um exemplo prático robusto

A natureza genérica do exemplo fornecido é difícil de falar. Eu criei um componente que demonstra um uso prático para a pergunta acima, implementado de uma maneira muito reativa :

Exemplo de trabalho do
DTServiceCalculator DTServiceCalculator repo

Este componente é uma calculadora de serviço simples. Você fornece uma lista de serviços (com nomes e preços) e calcula um total dos preços selecionados.

As crianças são alegremente ignorantes

ServiceItemé o componente filho neste exemplo. Não tem muitas opiniões sobre o mundo exterior. Ele requer alguns adereços , uma das quais é uma função a ser chamada quando clicado.

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

Ele não faz nada além de chamar o handleClickretorno de chamada fornecido com a index[ origem ] fornecida .

Os pais são filhos

DTServicesCalculatoré o componente pai é este exemplo. Também é uma criança. Vamos olhar.

DTServiceCalculatorcria uma lista de componentes filhos ServiceIteme fornece a eles adereços [ origem ]. É o componente pai de, ServiceItemmas é o componente filho do componente que está passando na lista. Não possui os dados. Por isso, novamente delega a manipulação do componente à sua origem do componente pai

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

handleServiceItemcaptura o índice, transmitido do filho e o fornece ao pai [ origem ]

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

Os proprietários sabem tudo

O conceito de "Propriedade" é importante no React. Eu recomendo ler mais sobre isso aqui .

No exemplo que mostrei, continuo delegando a manipulação de um evento na árvore de componentes até chegarmos ao componente que possui o estado.

Quando finalmente chegamos lá, lidamos com a seleção / desmarcação de estado da seguinte forma [ fonte ]:

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


Conclusão

Tente manter os componentes mais externos o mais opaco possível. Esforce-se para garantir que eles tenham muito poucas preferências sobre como um componente pai pode optar por implementá-los.

Lembre-se de quem possui os dados que você está manipulando. Na maioria dos casos, você precisará delegar eventos que manipulam a árvore para o componente que possui esse estado.

Além disso: o padrão Flux é uma boa maneira de reduzir esse tipo de conexão necessária nos aplicativos.

chantastic
fonte
Obrigado pela resposta concisa, isso realmente me ajudou a entender Reagir um pouco melhor! Eu tenho uma pergunta na mesma linha. Estou usando o Flux para pub / sub. Em vez de passar o manipulador de eventos filho para o pai como no seu exemplo, é possível implementar isso como uma 'ação' e ouvi-lo. Você consideraria isso uma boa alternativa e uso do Flux?
Pathsofdesign
1
Obrigado @Pathsofdesign! Isso dependeria. O fluxo tem esse conceito de visualizações de controladores. Neste exemplo, Parentpode ser uma visão do controlador, enquanto Childé apenas uma visão idiota (componente). Somente Controller-Views deve ter conhecimento do aplicativo. Nesse caso, você ainda passaria a ação de Parentpara Childcomo um suporte. No que diz respeito à própria Ação, o Flux possui um padrão fortemente prescrito para interagir entre as Ações para atualizar as Visualizações. facebook.github.io/flux/docs/…
chantastic 4/04
1
onClick={this.onClick.bind(null, this.state.text)}não há necessidade de vincular o estado, pois o pai tem isso. então só onClick={this.onClick}vai funcionar. onde onClick = ()=>{const text = this.state.text;..}no pai
Shishir Arora
25

Parece que há uma resposta simples. Considere isto:

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

A chave está chamando bind(null, this)o this.props.onClickevento, passado pelo pai. Agora, a função onClick aceita argumentos component, AND event. Eu acho que é o melhor de todos os mundos.

ATUALIZAÇÃO: 1/9/2015

Essa foi uma péssima idéia: deixar os detalhes da implementação filho vazarem para o pai nunca foi um bom caminho. Veja a resposta de Sebastien Lorber.

KendallB
fonte
os argumentos não estão do jeito errado (lugares trocados)?
O Surrican
1
@TheSurrican Não, está perfeitamente bem. O primeiro argumento é vinculado à thisfunção (que não é necessária de qualquer maneira) e o segundo é anexado como primeiro argumento quando onClick é chamado.
manmal 21/02/2015
13

A questão é como passar o argumento do componente filho para o componente pai. Este exemplo é fácil de usar e testado:

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

Veja JSFIDDLE

romano
fonte
No meu caso, esqueça a função de retorno de chamada de passagem para click ()
Long Nguyen
6

Basicamente, você usa adereços para enviar informações de e para Filhos e Pais.

Adicionando a todas as respostas maravilhosas, deixe-me dar um exemplo simples que explica a passagem de valores do componente filho para o componente pai no 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'));

É isso, agora você pode passar valores do seu cliente para o servidor.

Dê uma olhada na função Alterar no Header.js

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

É assim que você insere valores nos objetos do cliente para o servidor

Inácio André
fonte
Posso saber Isto é como você empurrar valores para os adereços do cliente para o servidor, o que você quer dizer com esta afirmação
G1P
2

Aqui está uma implementação simples do ES6 em três etapas usando a ligação de função no construtor pai. Esta é a primeira maneira que o tutorial oficial de reação recomenda (também há sintaxe de campos de classe pública não abordada aqui). Você pode encontrar todas essas informações aqui https://reactjs.org/docs/handling-events.html

Vinculando as funções dos pais para que as crianças possam chamá-las (e passar dados para os pais!: D)

  1. Certifique-se de que no construtor pai vincule a função criada no pai
  2. Passe a função vinculada para a criança como um suporte (sem lambda porque estamos passando um ref para a função)
  3. Chame a função vinculada de um evento filho (Lambda! Estamos chamando a função quando o evento é disparado. Se não fizermos isso, a função será executada automaticamente quando carregada e não será acionada no evento.)

Função dos pais

handleFilterApply(filterVals){} 

Construtor pai

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

Suporte passado para criança

onApplyClick = {this.handleFilterApply}

Chamada de evento filho

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

Este é um exemplo sem usar o evento onClick. Simplesmente passo uma função de retorno de chamada para a criança por adereços. Com esse retorno de chamada, a chamada secundária também envia os dados de volta. Eu fui inspirado pelos exemplos nos documentos .

Exemplo pequeno (este é um arquivo tsx, portanto, props e estados devem ser declarados completamente, excluí alguma lógica dos componentes, portanto, é menos código).

* Atualização: Importante é vincular isso ao retorno de chamada, caso contrário, o retorno de chamada tem o escopo do filho e não do pai. Único problema: é o pai "velho" ...

SymptomChoser é o pai:

interface SymptomChooserState {
  // true when a symptom was pressed can now add more detail
  isInDetailMode: boolean
  // since when user has this symptoms
  sinceDate: Date,
}

class SymptomChooser extends Component<{}, SymptomChooserState> {

  state = {
    isInDetailMode: false,
    sinceDate: new Date()
  }

  helloParent(symptom: Symptom) {
    console.log("This is parent of: ", symptom.props.name);
    // TODO enable detail mode
  }

  render() {
    return (
      <View>
        <Symptom name='Fieber' callback={this.helloParent.bind(this)} />
      </View>
    );
  }
}

Sintoma é o filho (nos objetos do filho que eu declarei a função de retorno de chamada, na função selectedSymptom o retorno de chamada é chamado):

interface SymptomProps {
  // name of the symptom
  name: string,
  // callback to notify SymptomChooser about selected Symptom.
  callback: (symptom: Symptom) => void
}

class Symptom extends Component<SymptomProps, SymptomState>{

  state = {
    isSelected: false,
    severity: 0
  }

  selectedSymptom() {
    this.setState({ isSelected: true });
    this.props.callback(this);
  }

  render() {
    return (
      // symptom is not selected
      <Button
        style={[AppStyle.button]}
        onPress={this.selectedSymptom.bind(this)}>
        <Text style={[AppStyle.textButton]}>{this.props.name}</Text>
      </Button>
    );
  }
}
drewjosh
fonte