ReactJS Dois componentes se comunicando


321

Acabei de começar o ReactJS e estou um pouco preso em um problema que tenho.

Minha aplicação é essencialmente uma lista com filtros e um botão para alterar o layout. No momento eu estou usando três componentes: <list />, < Filters />e <TopBar />, agora, obviamente, quando eu alterar as configurações em < Filters />que deseja acionar algum método em <list />atualizar meu ponto de vista.

Como posso fazer com que esses três componentes interajam, ou preciso de algum tipo de modelo de dados global para o qual eu possa fazer alterações?


Os três irmãos são componentes ou um dentro do outro?
precisa saber é o seguinte

Eles são todos os três componentes. Já reorganizei meu aplicativo para que agora todos tenham o mesmo pai que pode fornecer dados.
woutr_be 24/01

4
É aqui que você pode usar o padrão de fluxo ou pubsub. Com base nos documentos em documentos reagidos, eles deixam uma frase um tanto ambígua: "Para a comunicação entre dois componentes que não têm um relacionamento pai-filho, você pode configurar seu próprio sistema de eventos globais". facebook.github.io/react/tips/...
BingeBoy

@BingeBoy está certo O Flux é uma ótima maneira de escrever aplicativos reactjs, que podem lidar com o problema do fluxo de dados, compartilhamento de dados por muitos componentes.
Ankit Patial

4
Se você não quiser entrar na Flux ou Redux, este é um artigo incrível sobre este tema andrewhfarmer.com/component-communication
garajo

Respostas:


318

A melhor abordagem dependeria de como você planeja organizar esses componentes. Alguns exemplos de cenários que vêm à mente agora:

  1. <Filters /> é um componente filho de <List />
  2. Ambos <Filters />e <List />são filhos de um componente pai
  3. <Filters />e <List />viva inteiramente em componentes raiz separados.

Pode haver outros cenários em que não estou pensando. Se o seu não se encaixar nesses, informe-me. Aqui estão alguns exemplos aproximados de como eu lidei com os dois primeiros cenários:

Cenário 1

Você pode passar um manipulador de <List />para <Filters />, que pode ser chamado no onChangeevento para filtrar a lista com o valor atual.

JSFiddle para # 1 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    var content;
    if (displayedItems.length > 0) {
      var items = displayedItems.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

React.renderComponent(<List />, document.body);

Cenário # 2

Semelhante ao cenário nº 1, mas será o componente pai que passa a função manipuladora <Filters />e passará a lista filtrada para <List />. Eu gosto mais desse método, pois desacopla o <List />do <Filters />.

JSFiddle para # 2 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  render: function() {
    var content;
    if (this.props.items.length > 0) {
      var items = this.props.items.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }
    return (
      <div className="results">
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

var ListContainer = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    );
  }
});

React.renderComponent(<ListContainer />, document.body);

Cenário # 3

Quando os componentes não podem se comunicar entre nenhum tipo de relacionamento pai-filho, a documentação recomenda a configuração de um sistema global de eventos .


6
A coisa agradável com # 2 é que eles só dependem de um pai que passa um suporte para cada componente: uma função como updateFiltera <Filters />e uma matriz como itemsa <List />. Você pode usar esses componentes filhos em outros pais com comportamentos diferentes, juntos ou sozinhos. Por exemplo, se você queria exibir uma lista dinâmica, mas não precisava de filtragem.
Michael LaCroix

2
@woutr_be Não tenho certeza se isso se encaixaria nos seus requisitos, mas quando em uma situação semelhante em algum momento, usamos duas funções a seguir para classificar a comunicação entre os componentes filho e pai: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Espero que ajude! (desculpe não poderia formatá-lo melhor)
5122014009

29
Para o cenário 3, existe uma abordagem recomendada? Algum documento ou exemplo disso ao gerar eventos sintéticos personalizados? Não encontrei nada nos documentos principais.
pwray

1
O cenário nº 2 faz muito sentido ... até que você precise comprometer o design (se houver, Layout) - então você percebe a necessidade de um EventHub / PubSub.
Cody

4
O link do cenário nº 3 está inoperante e redireciona para uma página de documentos do React não relacionada agora.
beporter

170

Existem várias maneiras de fazer com que os componentes se comuniquem. Alguns podem ser adequados ao seu caso de usuário. Aqui está uma lista de alguns que eu achei úteis.

Reagir

Comunicação direta entre pais e filhos

const Child = ({fromChildToParentCallback}) => (
  <div onClick={() => fromChildToParentCallback(42)}>
    Click me
  </div>
);

class Parent extends React.Component {
  receiveChildValue = (value) => {
    console.log("Parent received value from child: " + value); // value is 42
  };
  render() {
    return (
      <Child fromChildToParentCallback={this.receiveChildValue}/>
    )
  }
}

Aqui, o componente filho chamará um retorno de chamada fornecido pelo pai com um valor, e o pai poderá obter o valor fornecido pelos filhos no pai.

Se você criar um recurso / página do seu aplicativo, é melhor ter um pai solteiro gerenciando os retornos de chamada / estado (também chamado de ) containerou que smart componenttodos os filhos sejam apátridas, relatando apenas as coisas ao pai. Dessa forma, você pode "compartilhar" facilmente o estado dos pais com qualquer criança que precise dele.


Contexto

O React Context permite manter o estado na raiz da hierarquia de componentes e ser capaz de injetar esse estado facilmente em componentes muito profundamente aninhados, sem a necessidade de passar adereços para todos os componentes intermediários.

Até agora, o contexto era um recurso experimental, mas uma nova API está disponível no React 16.3.

const AppContext = React.createContext(null)

class App extends React.Component {
  render() {
    return (
      <AppContext.Provider value={{language: "en",userId: 42}}>
        <div>
          ...
          <SomeDeeplyNestedComponent/>
          ...
        </div>
      </AppContext.Provider>
    )
  }
};

const SomeDeeplyNestedComponent = () => (
  <AppContext.Consumer>
    {({language}) => <div>App language is currently {language}</div>}
  </AppContext.Consumer>
);

O consumidor está usando o padrão de função prop prop / children

Confira esta postagem no blog para obter mais detalhes.

Antes do React 16.3, eu recomendaria usar o react-broadcast, que oferece API bastante semelhante, e usar a API de contexto anterior.


Portais

Use um portal quando desejar manter 2 componentes próximos para fazê-los se comunicar com funções simples, como no pai / filho normal, mas você não deseja que esses 2 componentes tenham um relacionamento pai / filho no DOM, porque das restrições visuais / CSS que isso implica (como z-index, opacidade ...).

Nesse caso, você pode usar um "portal". Existem diferentes bibliotecas de reação usando portais , geralmente usados ​​para modais , pop-ups, dicas de ferramentas ...

Considere o seguinte:

<div className="a">
    a content
    <Portal target="body">
        <div className="b">
            b content
        </div>
    </Portal>
</div>

Pode produzir o seguinte DOM quando renderizado dentro reactAppContainer:

<body>
    <div id="reactAppContainer">
        <div className="a">
             a content
        </div>
    </div>
    <div className="b">
         b content
    </div>
</body>

Mais detalhes aqui


Slots

Você define um slot em algum lugar e preenche o slot a partir de outro local da sua árvore de renderização.

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="ToolbarContent" />
  </div>

export default Toolbar;

export const FillToolbar = ({children}) =>
  <Fill name="ToolbarContent">
    {children}
  </Fill>

Isso é um pouco semelhante aos portais, exceto que o conteúdo preenchido será renderizado em um slot que você definir, enquanto os portais geralmente renderizam um novo nó dom (geralmente filhos de document.body)

Verifique a biblioteca react-slot-fill


Barramento de evento

Conforme indicado na documentação do React :

Para comunicação entre dois componentes que não têm um relacionamento pai-filho, é possível configurar seu próprio sistema de eventos global. Inscreva-se nos eventos em componentDidMount (), cancele a inscrição em componentWillUnmount () e chame setState () quando receber um evento.

Há muitas coisas que você pode usar para configurar um barramento de eventos. Você pode simplesmente criar uma matriz de ouvintes e, na publicação do evento, todos os ouvintes receberiam o evento. Ou você pode usar algo como EventEmitter ou PostalJs


Fluxo

O fluxo é basicamente um barramento de eventos, exceto que os receptores de eventos são lojas. É semelhante ao sistema de barramento de eventos básico, exceto que o estado é gerenciado fora do React

A implementação original do Flux parece uma tentativa de obter fontes de eventos de maneira hacky.

O Redux é para mim a implementação do Flux que é a mais próxima da fonte de eventos, beneficiando muitas vantagens da fonte de eventos, como a capacidade de viajar no tempo. Não está estritamente vinculado ao React e também pode ser usado com outras bibliotecas de visualizações funcionais.

O tutorial em vídeo Redux da Egghead é muito bom e explica como funciona internamente (é realmente simples).


Cursores

Os cursores são provenientes do ClojureScript / Om e são amplamente utilizados em projetos React. Eles permitem gerenciar o estado fora do React e permitem que vários componentes tenham acesso de leitura / gravação à mesma parte do estado, sem precisar saber nada sobre a árvore de componentes.

Existem muitas implementações, incluindo ImmutableJS , React-cursors e Omniscient

Edit 2016 : parece que as pessoas concordam que os cursores funcionam bem em aplicativos menores, mas não se adaptam bem a aplicativos complexos. O Om Next não possui mais cursores (enquanto o Om introduziu o conceito inicialmente)


Arquitetura do olmo

A arquitetura Elm é uma arquitetura proposta para ser usada pela linguagem Elm . Mesmo que o Elm não seja o ReactJS, a arquitetura do Elm também pode ser feita no React.

Dan Abramov, autor do Redux, fez uma implementação da arquitetura Elm usando o React.

O Redux e o Elm são realmente ótimos e tendem a fortalecer os conceitos de fornecimento de eventos no frontend, permitindo a depuração de viagens no tempo, desfazer / refazer, repetir ...

A principal diferença entre Redux e Elm é que Elm tende a ser muito mais rigoroso em relação à administração do estado. No Elm, você não pode ter o estado do componente local ou montar / desmontar ganchos e todas as alterações do DOM devem ser acionadas por alterações globais do estado. A arquitetura Elm propõe uma abordagem escalável que permite manipular TODO o estado dentro de um único objeto imutável, enquanto o Redux propõe uma abordagem que convida você a manipular a maior parte do estado em um único objeto imutável.

Embora o modelo conceitual do Elm seja muito elegante e a arquitetura permita escalar bem em aplicativos grandes, na prática pode ser difícil ou envolver mais clichês para realizar tarefas simples, como focar uma entrada após a montagem ou integrar-se a uma biblioteca existente com uma interface imperativa (ou seja, plug-in JQuery). Assunto relacionado .

Além disso, a arquitetura Elm envolve mais clichês de código. Não é tão detalhado ou complicado de escrever, mas acho que a arquitetura Elm é mais adequada para linguagens estaticamente tipadas.


FRP

Bibliotecas como RxJS, BaconJS ou Kefir podem ser usadas para produzir fluxos FRP para lidar com a comunicação entre componentes.

Você pode tentar, por exemplo, Rx-React

Eu acho que o uso dessas bibliotecas é bastante semelhante ao uso do que a linguagem ELM oferece com sinais .

A estrutura CycleJS não usa ReactJS, mas usa vdom . Ele compartilha muitas semelhanças com a arquitetura Elm (mas é mais fácil de usar na vida real porque permite ganchos vdom) e usa RxJs extensivamente em vez de funções, e pode ser uma boa fonte de inspiração se você quiser usar o FRP com Reagir. Os vídeos do CycleJs Egghead são bons para entender como ele funciona.


CSP

Atualmente, o CSP (Communication Sequential Processes) é popular (principalmente por causa de Go / goroutines e core.async / ClojureScript), mas você pode usá-los também em javascript com JS-CSP .

James Long fez um vídeo explicando como ele pode ser usado com o React.

Sagas

Uma saga é um conceito de back-end que vem do mundo DDD / EventSourcing / CQRS, também chamado de "gerenciador de processos". Ele está sendo popularizado pelo projeto redux-saga , principalmente como um substituto ao redux-thunk para lidar com efeitos colaterais (por exemplo, chamadas de API etc.). Atualmente, a maioria das pessoas pensa que isso serve apenas para efeitos colaterais, mas na verdade é mais sobre dissociação de componentes.

É mais um elogio a uma arquitetura Flux (ou Redux) do que a um sistema de comunicação totalmente novo, porque a saga emite ações de Flux no final. A ideia é que, se você tiver o widget1 e o widget2 e desejar que eles sejam dissociados, não poderá disparar o widget2 de direcionamento de ação do widget1. Portanto, você faz apenas o widget1 disparar ações direcionadas a si mesmas, e a saga é um "processo em segundo plano" que escuta as ações do widget1 e pode despachar ações direcionadas ao widget2. A saga é o ponto de acoplamento entre os 2 widgets, mas os widgets permanecem desacoplados.

Se você estiver interessado, dê uma olhada na minha resposta aqui


Conclusão

Se você quiser ver um exemplo do mesmo pequeno aplicativo usando esses estilos diferentes, verifique as ramificações deste repositório .

Não sei qual é a melhor opção a longo prazo, mas realmente gosto da aparência do Flux como fonte de eventos.

Se você não conhece os conceitos de fornecimento de eventos, dê uma olhada neste blog muito pedagógico: Revolvendo o banco de dados com o apache Samza , é uma leitura obrigatória para entender por que o Flux é bom (mas isso também se aplica ao FRP) )

Acho que a comunidade concorda que a implementação mais promissora do Flux é o Redux , que permitirá progressivamente uma experiência produtiva do desenvolvedor, graças ao recarregamento a quente. A codificação ao vivo impressionante, ao lado de O vídeo Inventando sobre os Princípios de Bret Victor, é possível!


2
Excelente resposta Seb!
Ali Gajani 6/03/19

7

OK, existem poucas maneiras de fazer isso, mas eu quero exclusivamente focar no uso da loja usando o Redux, o que facilita muito a sua vida para essas situações, em vez de fornecer uma solução rápida apenas para esse caso, o uso do React puro vai acabar estragando tudo. aplicação real grande e comunicação entre componentes se torna cada vez mais difícil à medida que a aplicação cresce ...

Então, o que Redux faz por você?

O Redux é como o armazenamento local no seu aplicativo, que pode ser usado sempre que você precisar que os dados sejam usados ​​em diferentes locais do seu aplicativo ...

Basicamente, a ideia do Redux vem originalmente do fluxo, mas com algumas mudanças fundamentais, incluindo o conceito de ter uma fonte de verdade criando apenas uma loja ...

Veja o gráfico abaixo para ver algumas diferenças entre o Flux e o Redux ...

Redux e Flux

Considere aplicar o Redux no seu aplicativo desde o início, se ele precisar de comunicação entre os Componentes ...

Também a leitura destas palavras da Redux Documentation pode ser útil para começar:

Como os requisitos para aplicativos de página única JavaScript se tornam cada vez mais complicados, nosso código deve gerenciar mais estados do que nunca . Esse estado pode incluir respostas do servidor e dados em cache, além de dados criados localmente que ainda não foram mantidos no servidor. O estado da interface do usuário também está aumentando em complexidade, pois precisamos gerenciar rotas ativas, guias selecionadas, giradores, controles de paginação e assim por diante.

Gerenciar esse estado em constante mudança é difícil. Se um modelo pode atualizar outro modelo, uma exibição pode atualizar um modelo, que atualiza outro modelo, e isso, por sua vez, pode fazer com que outra exibição seja atualizada. Em algum momento, você não entende mais o que acontece no seu aplicativo porque perdeu o controle sobre quando, por que e como do seu estado. Quando um sistema é opaco e não determinístico, é difícil reproduzir bugs ou adicionar novos recursos.

Como se isso não bastasse, considere os novos requisitos se tornando comuns no desenvolvimento de produtos front-end. Como desenvolvedores, espera-se que lidemos com atualizações otimistas, renderização no servidor, buscando dados antes de realizar transições de rota e assim por diante. Nós nos encontramos tentando gerenciar uma complexidade com a qual nunca tivemos que lidar antes, e inevitavelmente fazemos a pergunta: é hora de desistir? A resposta é não.

Essa complexidade é difícil de lidar, pois estamos misturando dois conceitos que são muito difíceis para a mente humana pensar: mutação e assincronicidade. Eu os chamo de Mentos e Coca-Cola. Ambos podem ser ótimos em separação, mas juntos eles criam uma bagunça. Bibliotecas como React tentam resolver esse problema na camada de exibição removendo a assincronia e a manipulação direta do DOM. No entanto, o gerenciamento do estado dos seus dados fica a seu critério. É aqui que o Redux entra.

Seguindo as etapas do Flux, CQRS e Event Sourcing , o Redux tenta tornar previsíveis as mutações de estado, impondo certas restrições sobre como e quando as atualizações podem ocorrer . Essas restrições são refletidas nos três princípios do Redux.


Como o redux pode ajudar? se eu tiver um modal para a datepicker(como um componente) e esse componente puder ser carregado de vários componentes que vivem na mesma página, como o datepickercomponente saberia qual ação enviar para redux? Essa é a essência do problema, vinculando uma ação em um componente a outro e NÃO a qualquer outro componente . (levar em consideração a datepickersi é um componente muito, muito profunda dentro do próprio componente modal)
vsync

@vsync não acha reudx como um único valor estático, o redux é realmente pode ter objetos, matrizes ... para que você possa salvá-los como objeto ou matriz ou o que quer que seja na sua loja, pode ser mapdispatchtoprops e cada um deles é salvo na matriz de objetos como: [{name: "picker1", valor: "01/01/1970"}, {name: "picker2", valor: "01/01/1980"}] e, em seguida, use mapstatetoprops no pai e transmita-o para cada componente ou onde você quiser, não tenho certeza se responde à sua pergunta, mas sem ver o código ... se eles estiverem em grupos separados, você também pode objetar com mais detalhes ... mas tudo depende de como você deseja agrupar eles ..
Alireza 9/10

A questão não é sobre o reduxque você pode armazenar, mas como passar a ação profundamente para o que precisa ser acionado. Como um componente profundo sabe o que EXATAMENTE precisa ser acionado? Como no exemplo, forneci um componente geral que deve ser acionado para um redutor específico, dependendo do cenário, para que possa ser um redutor diferente, pois um modal de datapicker pode ser usado para qualquer componente.
vsync

5

Foi assim que lidei com isso.
Digamos que você tenha uma <seleção> para o mês e uma <seleção> para o dia . O número de dias depende do mês selecionado.

Ambas as listas pertencem a um terceiro objeto, o painel esquerdo. Ambos <select> também são filhos do leftPanel <div>
É um jogo com retornos de chamada e manipuladores no componente LeftPanel .

Para testá-lo, basta copiar o código em dois arquivos separados e executar o index.html . Em seguida, selecione um mês e veja como o número de dias muda.

datas.js

    /** @jsx React.DOM */


    var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

    var DayNumber = React.createClass({
        render: function() {
            return (
                <option value={this.props.dayNum}>{this.props.dayNum}</option>
            );
        }
    });

    var DaysList = React.createClass({
        getInitialState: function() {
            return {numOfDays: 30};
        },
        handleMonthUpdate: function(newMonthix) {
            this.state.numOfDays = monthsLength[newMonthix];
            console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);

            this.forceUpdate();
        },
        handleDaySelection: function(evt) {
            this.props.dateHandler(evt.target.value);
        },
        componentDidMount: function() {
            this.props.readyCallback(this.handleMonthUpdate)
        },
        render: function() {
            var dayNodes = [];
            for (i = 1; i <= this.state.numOfDays; i++) {
                dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
            }
            return (
                <select id={this.props.id} onChange = {this.handleDaySelection}>
                    <option value="" disabled defaultValue>Day</option>
                        {dayNodes}
                </select>
                );
        }
    });

    var Month = React.createClass({
        render: function() {
            return (
                <option value={this.props.monthIx}>{this.props.month}</option>
            );
        }
    });

    var MonthsList = React.createClass({
        handleUpdate: function(evt) {
            console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
            this.props.dateHandler(evt.target.value);

            return false;
        },
        render: function() {
            var monthIx = 0;

            var monthNodes = this.props.data.map(function (month) {
                monthIx++;
                return (
                    <Month month={month} monthIx={monthIx} />
                    );
            });

            return (
                <select id = {this.props.id} onChange = {this.handleUpdate}>
                    <option value="" disabled defaultValue>Month</option>
                        {monthNodes}
                </select>
                );
        }
    });

    var LeftPanel = React.createClass({
        dayRefresh: function(newMonth) {
            // Nothing - will be replaced
        },
        daysReady: function(refreshCallback) {
            console.log("Regisering days list");
        this.dayRefresh = refreshCallback;
        },
        handleMonthChange: function(monthIx) {
            console.log("New month");
            this.dayRefresh(monthIx);
        },
        handleDayChange: function(dayIx) {
            console.log("New DAY: " + dayIx);
        },
        render: function() {
            return(
                <div id="orderDetails">
                    <DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
                    <MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange}  />
                </div>
            );
        }
    });



    React.renderComponent(
        <LeftPanel />,
        document.getElementById('leftPanel')
    );

E o HTML para executar o componente esquerdo do painel index.html

<!DOCTYPE html>
<html>
<head>
    <title>Dates</title>

    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <script src="//fb.me/react-0.11.1.js"></script>
    <script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>

    <style>

        #dayPicker {
            position: relative;
            top: 97px;
            left: 20px;
            width: 60px;
            height: 17px;
        }

        #monthPicker {
            position: relative;
            top: 97px;
            left: 22px;
            width: 95px;
            height: 17px;
        }

        select {
            font-size: 11px;
        }

    </style>


    <body>
        <div id="leftPanel">
        </div>

        <script type="text/jsx" src="dates.js"></script>

    </body>
</html>

se você pudesse excluir 80% do código de exemplo e ainda manter seu argumento, seria melhor. mostrando CSS no contexto deste tópico, é irrelevante
vsync

3

Vi que a pergunta já foi respondida, mas se você quiser saber mais detalhes, há um total de três casos de comunicação entre componentes :

  • Caso 1: Comunicação entre pais e filhos
  • Caso 2: Comunicação entre pais e filhos
  • Caso 3: Comunicação de componentes não relacionados (qualquer componente para qualquer componente)

1

Estendendo a resposta de @MichaelLaCroix quando um cenário é que os componentes não podem se comunicar entre nenhum tipo de relacionamento pai-filho, a documentação recomenda a configuração de um sistema de eventos global.

No caso de <Filters />e <TopBar />não possui nenhuma das relações acima, um emissor global simples pode ser usado assim:

componentDidMount - Inscrever-se no evento

componentWillUnmount - Cancelar inscrição do evento

Código React.js e EventSystem

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

NotificationComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

0

Existe essa possibilidade, mesmo que eles não sejam um relacionamento pai - filho - e esse seja o fluxo. Existe uma implementação muito boa (para mim pessoalmente) para o chamado Alt.JS (com o Alt-Container).

Por exemplo, você pode ter a Barra Lateral que depende do que está definido nos Detalhes do componente. A Barra Lateral do Componente está conectada com SidebarActions e SidebarStore, enquanto Detalhes é DetailsActions e DetailsStore.

Você pode usar o AltContainer assim

<AltContainer stores={{
                    SidebarStore: SidebarStore
                }}>
                    <Sidebar/>
</AltContainer>

{this.props.content}

O que manteria as lojas (bem, eu poderia usar "store" em vez de "stores" prop). Agora, {this.props.content} PODE SER Detalhes dependendo da rota. Digamos que / Detalhes nos redirecionam para essa visualização. Os detalhes teriam, por exemplo, uma caixa de seleção que alteraria o elemento da Barra Lateral de X para Y se fosse marcado.

Tecnicamente, não há relação entre eles e seria difícil prescindir do fluxo. Mas com isso é bastante fácil.

Agora vamos para DetailsActions. Vamos criar lá

class SiteActions {
constructor() {
    this.generateActions(
        'setSiteComponentStore'
    );
}

setSiteComponent(value) {
    this.dispatch({value: value});
}
}

e DetailsStore

class SiteStore {
constructor() {
    this.siteComponents = {
        Prop: true
    };

    this.bindListeners({
        setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

setSiteComponent(data) {
    this.siteComponents.Prop = data.value;
}
}

E agora, este é o lugar onde a mágica começa.

Como você pode ver, existe bindListener em SidebarActions.ComponentStatusChanged que será usado SE setSiteComponent será usado.

agora em SidebarActions

    componentStatusChanged(value){
    this.dispatch({value: value});
}

Nós temos tal coisa. Ele enviará esse objeto de plantão. E será chamado se setSiteComponent in store for usado (que você pode usar no componente, por exemplo, durante onChange no botão ot o que for)

Agora, na SidebarStore, teremos

    constructor() {
    this.structures = [];

    this.bindListeners({
        componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

    componentStatusChanged(data) {
    this.waitFor(DetailsStore);

    _.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}

Agora, aqui você pode ver que ele aguardará a DetailsStore. O que isso significa? mais ou menos, significa que esse método precisa aguardar a atualização DetailsStor para que ele possa se atualizar.

O One Store está ouvindo os métodos em uma loja e acionará uma ação da ação do componente, que atualizará sua própria loja.

Espero que possa ajudá-lo de alguma forma.


0

Se você quiser explorar as opções de comunicação entre componentes e sentir que está ficando cada vez mais difícil, considere adotar um bom padrão de design: Flux .

É simplesmente uma coleção de regras que define como você armazena e altera o estado amplo do aplicativo e usa esse estado para renderizar componentes.

Existem muitas implementações do Flux, e a implementação oficial do Facebook é uma delas. Embora seja considerado aquele que contém a maioria dos códigos padrão, mas é mais fácil entender, pois a maioria das coisas é explícita.

Alguns Outras alternativas são flummox fluxxor fluxible e redux .


0

Uma vez eu estava onde você está agora, como iniciante às vezes se sente deslocado sobre a maneira como reage a isso. Vou tentar abordar da mesma maneira que penso nisso agora.

Estados são a pedra angular da comunicação

Geralmente, o que se resume é a maneira como você altera os estados nesse componente. No seu caso, você aponta três componentes.

<List />: Que provavelmente exibirá uma lista de itens, dependendo de um filtro <Filters />: Opções de filtro que alterarão seus dados. <TopBar />: Lista de opções.

Para orquestrar toda essa interação, você precisará de um componente superior, vamos chamá-lo de App, que transmitirá ações e dados para cada um desses componentes, para que, por exemplo, possa ser assim

<div>
  <List items={this.state.filteredItems}/>
  <Filter filter={this.state.filter} setFilter={setFilter}/>
</div>

Portanto, quando setFilteré chamado, ele afetará o filterItem e renderizará novamente o componente ;. Caso isso não esteja totalmente claro, fiz um exemplo com a caixa de seleção que você pode verificar em um único arquivo:

import React, {Component} from 'react';
import {render} from 'react-dom';

const Person  = ({person, setForDelete}) => (
          <div>
            <input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
            {person.name}
          </div>
);


class PeopleList extends Component {

  render() {

    return(
      <div>
       {this.props.people.map((person, i) => {
         return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
       })}
       <div onClick={this.props.deleteRecords}>Delete Selected Records</div>
     </div>
    );
  }

} // end class

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
  }

  deleteRecords() {
    const people = this.state.people.filter(p => !p.checked);

    this.setState({people});
 }

  setForDelete(person) {
    const checked = !person.checked;
    const people = this.state.people.map((p)=>{
      if(p.id === person.id)
        return {name:person.name, checked};
      return p;
    });

    this.setState({people});
  }

  render () {

    return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
  }
}

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

0

O código a seguir me ajuda a configurar a comunicação entre dois irmãos. A configuração é feita em seus pais durante as chamadas render () e componentDidMount (). É baseado em https://reactjs.org/docs/refs-and-the-dom.html Espero que ajude.

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}

Este é o TypeScript, provavelmente deve ser mencionado em sua resposta. Bom conceito embora.
serraosays 2/02/19

0

Estranhamente, ninguém mencionou mobx. A ideia é semelhante a redux. Se eu tiver um dado em que vários componentes estão inscritos, eu posso usar esses dados para direcionar vários componentes.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.