ReactJS Dos componentes comunicando

321

Acabo de comenzar con ReactJS y estoy un poco atascado en un problema que tengo.

Mi aplicación es esencialmente una lista con filtros y un botón para cambiar el diseño. Por el momento estoy usando tres componentes: <list />, < Filters />y <TopBar />, ahora, obviamente, cuando cambio de configuración en el < Filters />que quiero para desencadenar algún método de <list />actualizar mi punto de vista.

¿Cómo puedo hacer que esos 3 componentes interactúen entre sí, o necesito algún tipo de modelo de datos global en el que pueda hacer cambios?

woutr_be
fuente
¿Son los tres componentes hermanos o están uno dentro del otro?
pgreen2
Son los tres componentes, ya he reorganizado mi aplicación para que ahora todos tengan el mismo padre que pueda proporcionarles datos.
woutr_be
44
Aquí es donde podría usar el patrón de flujo o pubsub. Según los documentos de los documentos de reacción, dejan una frase algo ambigua: "Para la comunicación entre dos componentes que no tienen una relación padre-hijo, puede configurar su propio sistema de eventos global". facebook.github.io/react/tips/…
BingeBoy
@BingeBoy tiene razón Flux es una excelente manera de escribir aplicaciones reactjs, que pueden manejar el problema del flujo de datos, el intercambio de datos por muchos componentes.
Ankit Patial
44
Si no quiere entrar en Flux o Redux, este es un artículo increíble sobre este tema andrewhfarmer.com/component-communication
garajo

Respuestas:

318

El mejor enfoque dependerá de cómo planee organizar esos componentes. Algunos ejemplos de escenarios que me vienen a la mente en este momento:

  1. <Filters /> es un componente hijo de <List />
  2. Ambos <Filters />y <List />son hijos de un componente padre
  3. <Filters />y <List />vivir en componentes raíz separados por completo.

Puede haber otros escenarios en los que no estoy pensando. Si el tuyo no se ajusta a estos, avísame Aquí hay algunos ejemplos muy generales de cómo he estado manejando los dos primeros escenarios:

Escenario 1

Puede pasar un controlador de <List />a <Filters />, que luego se puede llamar en el onChangeevento para filtrar la lista con el valor actual.

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

Escenario # 2

Similar al escenario # 1, pero el componente principal será el que pasará la función del controlador a <Filters />, y pasará la lista filtrada a <List />. Me gusta más este método ya que desacopla el <List />del <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);

Escenario # 3

Cuando los componentes no pueden comunicarse entre ningún tipo de relación padre-hijo, la documentación recomienda configurar un sistema de eventos global .

Michael LaCroix
fuente
66
Lo bueno con # 2 es que sólo se basan en un padre que pasa un apoyo a cada componente: una función que updateFiltera <Filters />y una matriz como itemsa <List />. Podrías usar esos componentes hijos en otros padres con diferentes comportamientos, ya sea juntos o solos. Por ejemplo, si desea mostrar una lista dinámica pero no necesita filtrado.
Michael LaCroix
2
@woutr_be No estoy seguro de si encajaría en su requerimiento, pero en una situación similar en algún momento, utilizamos las siguientes dos funciones para comunicarnos entre los componentes hijo y padre: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} ¡Espero que ayude! (lo siento, no podría formatearlo mejor)
5122014009
29
Para el escenario 3, ¿hay un enfoque recomendado? ¿Algún documento o ejemplo sobre esto generando eventos sintéticos personalizados? No he encontrado nada en los documentos principales.
pwray
1
El escenario n. ° 2 tiene mucho sentido ... hasta que tenga que poner en peligro el diseño (aunque solo sea el diseño), entonces se da cuenta de la necesidad de un EventHub / PubSub.
Cody
44
El enlace del escenario n. ° 3 está inactivo y ahora redirige a una página de React docs no relacionada.
beporter
170

Hay múltiples formas de hacer que los componentes se comuniquen. Algunos pueden ser adecuados para su caso de uso. Aquí hay una lista de algunos que he encontrado útiles para conocer.

Reaccionar

Comunicación directa padre / hijo

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

Aquí el componente hijo llamará a una devolución de llamada proporcionada por el padre con un valor, y el padre podrá obtener el valor proporcionado por los niños en el padre.

Si crea una función / página de su aplicación, es mejor tener un padre único que administre las devoluciones de llamada / estado (también llamado containero smart component), y que todos los niños sean apátridas, solo informando cosas al padre. De esta manera, puede "compartir" fácilmente el estado del padre con cualquier hijo que lo necesite.


Contexto

React Context permite mantener el estado en la raíz de la jerarquía de sus componentes y poder inyectar este estado fácilmente en componentes muy anidados, sin la molestia de tener que pasar accesorios a cada componente intermedio.

Hasta ahora, el contexto era una característica experimental, pero una nueva API está disponible en 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>
);

El consumidor está utilizando el patrón de función render prop / children

Consulte esta publicación de blog para más detalles.

Antes de React 16.3, recomendaría usar react-broadcast, que ofrece una API bastante similar, y una API de contexto anterior.


Portales

Use un portal cuando desee mantener 2 componentes juntos para que se comuniquen con funciones simples, como en padre / hijo normal, pero no desea que estos 2 componentes tengan una relación padre / hijo en el DOM, porque de las restricciones visuales / CSS que implica (como el índice z, la opacidad ...).

En este caso puede usar un "portal". Existen diferentes bibliotecas de reacción que utilizan portales , generalmente utilizados para modales , ventanas emergentes, información sobre herramientas ...

Considera lo siguiente:

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

Podría producir el siguiente DOM cuando se representa en el interior reactAppContainer:

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

Más detalles aquí


Tragamonedas

Define una ranura en algún lugar y luego llena la ranura desde otro lugar de su árbol de renderizado.

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>

Esto es un poco similar a los portales, excepto que el contenido lleno se representará en un espacio que usted defina, mientras que los portales generalmente representan un nuevo nodo dom (a menudo un elemento secundario de document.body)

Verifique la biblioteca react-slot-fill


Bus de eventos

Como se indica en la documentación de React :

Para la comunicación entre dos componentes que no tienen una relación padre-hijo, puede configurar su propio sistema global de eventos. Suscríbase a los eventos en componentDidMount (), anule la suscripción en componentWillUnmount () y llame a setState () cuando reciba un evento.

Hay muchas cosas que puede usar para configurar un bus de eventos. Puede crear una variedad de oyentes, y en la publicación del evento, todos los oyentes recibirían el evento. O puede usar algo como EventEmitter o PostalJs


Flujo

Flux es básicamente un bus de eventos, excepto que los receptores de eventos son tiendas. Esto es similar al sistema de bus de eventos básico, excepto que el estado se administra fuera de React

La implementación original de Flux se parece a un intento de hacer un abastecimiento de eventos de una manera hacky.

Redux es para mí la implementación de Flux que es la más cercana al abastecimiento de eventos, y beneficia muchas de las ventajas del abastecimiento de eventos, como la capacidad de viajar en el tiempo. No está estrictamente vinculado a React y también se puede usar con otras bibliotecas de vistas funcionales.

El video tutorial de Redhead de Egghead es realmente agradable y explica cómo funciona internamente (es realmente simple).


Cursores

Los cursores provienen de ClojureScript / Om y se usan ampliamente en proyectos React. Permiten administrar el estado fuera de React y permiten que múltiples componentes tengan acceso de lectura / escritura a la misma parte del estado, sin necesidad de saber nada sobre el árbol de componentes.

Existen muchas implementaciones, incluidas ImmutableJS , React-cursores y Omniscient

Editar 2016 : parece que las personas están de acuerdo en que los cursores funcionan bien para aplicaciones más pequeñas, pero no se ajustan bien en aplicaciones complejas. Om Next ya no tiene cursores (mientras que Om introdujo el concepto inicialmente)


Arquitectura de olmo

La arquitectura Elm es una arquitectura propuesta para ser utilizada por el lenguaje Elm . Incluso si Elm no es ReactJS, la arquitectura de Elm también se puede hacer en React.

Dan Abramov, el autor de Redux, hizo una implementación de la arquitectura Elm usando React.

Tanto Redux como Elm son realmente geniales y tienden a potenciar los conceptos de origen de eventos en la interfaz, lo que permite la depuración de viajes en el tiempo, deshacer / rehacer, reproducir ...

La principal diferencia entre Redux y Elm es que Elm tiende a ser mucho más estricto sobre la gestión del estado. En Elm no puede tener un estado de componente local o montar / desmontar ganchos y todos los cambios de DOM deben ser activados por cambios de estado globales. La arquitectura Elm propone un enfoque escalable que permite manejar TODO el estado dentro de un único objeto inmutable, mientras que Redux propone un enfoque que lo invita a manejar la MAYORÍA del estado en un solo objeto inmutable.

Si bien el modelo conceptual de Elm es muy elegante y la arquitectura permite escalar bien en aplicaciones grandes, en la práctica puede ser difícil o involucrar más repetitivo para lograr tareas simples como enfocar una entrada después de montarla o integrarse con una biblioteca existente con una interfaz imperativa (es decir, el complemento JQuery). Tema relacionado .

Además, la arquitectura Elm implica más código repetitivo. No es tan detallado o complicado de escribir, pero creo que la arquitectura Elm es más adecuada para los lenguajes de tipo estático.


FRP

Las bibliotecas como RxJS, BaconJS o Kefir se pueden usar para producir flujos FRP para manejar la comunicación entre componentes.

Puedes probar por ejemplo Rx-React

Creo que usar estas bibliotecas es bastante similar a usar lo que ofrece el lenguaje ELM con señales .

El marco de CycleJS no usa ReactJS pero usa vdom . Comparte muchas similitudes con la arquitectura Elm (pero es más fácil de usar en la vida real porque permite ganchos vdom) y usa RxJ ampliamente en lugar de funciones, y puede ser una buena fuente de inspiración si desea usar FRP con Reaccionar. Los videos de CycleJs Egghead son agradables de entender cómo funciona.


CSP

Los CSP (procesos secuenciales de comunicación) son populares actualmente (principalmente debido a Go / goroutines y core.async / ClojureScript), pero también puede usarlos en JavaScript con JS-CSP .

James Long ha hecho un video explicando cómo se puede usar con React.

Sagas

Una saga es un concepto de fondo que proviene del mundo DDD / EventSourcing / CQRS, también llamado "administrador de procesos". Está siendo popularizado por el proyecto redux-saga , principalmente como un reemplazo de redux-thunk para el manejo de efectos secundarios (es decir, llamadas API, etc.). La mayoría de las personas actualmente piensa que solo sirve para los efectos secundarios, pero en realidad se trata más de desacoplar componentes.

Es más un complemento de una arquitectura Flux (o Redux) que un sistema de comunicación totalmente nuevo, porque la saga emite acciones Flux al final. La idea es que si tiene widget1 y widget2, y desea que estén desacoplados, no puede disparar la acción de orientación widget2 desde widget1. Por lo tanto, hace que widget1 solo active acciones que se dirigen a sí mismo, y la saga es un "proceso en segundo plano" que escucha las acciones de widget1 y puede enviar acciones que se dirigen a widget2. La saga es el punto de acoplamiento entre los 2 widgets, pero los widgets permanecen desacoplados.

Si estás interesado mira mi respuesta aquí


Conclusión

Si desea ver un ejemplo de la misma pequeña aplicación que utiliza estos diferentes estilos, consulte las ramas de este repositorio .

No sé cuál es la mejor opción a largo plazo, pero realmente me gusta cómo se ve Flux como fuente de eventos.

Si no conoce los conceptos de abastecimiento de eventos, eche un vistazo a este blog muy pedagógico: dar la vuelta a la base de datos con apache Samza , es una lectura obligada para comprender por qué Flux es bueno (pero esto también podría aplicarse a FRP) )

Creo que la comunidad está de acuerdo en que la implementación de Flux más prometedora es Redux , que permitirá progresivamente una experiencia de desarrollo muy productiva gracias a la recarga en caliente. ¡Impresionante livecoding de ala Bret Victor's Inventing on Principle es posible!

Sebastien Lorber
fuente
2
Excelente respuesta Seb!
Ali Gajani
7

De acuerdo, hay pocas formas de hacerlo, pero quiero centrarme exclusivamente en usar la tienda usando Redux, lo que hace que tu vida sea mucho más fácil para estas situaciones en lugar de darte una solución rápida solo para este caso, usar React puro terminará en problemas Una aplicación realmente grande y la comunicación entre componentes se vuelve cada vez más difícil a medida que la aplicación crece ...

Entonces, ¿qué hace Redux por ti?

Redux es como el almacenamiento local en su aplicación que se puede usar siempre que necesite datos para usar en diferentes lugares de su aplicación ...

Básicamente, la idea de Redux proviene de Flux originalmente, pero con algunos cambios fundamentales, incluido el concepto de tener una fuente de verdad al crear una sola tienda ...

Mire el gráfico a continuación para ver algunas diferencias entre Flux y Redux ...

Redux y Flux

Considere aplicar Redux en su aplicación desde el principio si su aplicación necesita comunicación entre Componentes ...

También puede ser útil leer estas palabras de la documentación de Redux para comenzar:

Como los requisitos para las aplicaciones JavaScript de una sola página se han vuelto cada vez más complicados, nuestro código debe administrar más estado que nunca . Este estado puede incluir respuestas del servidor y datos en caché, así como datos creados localmente que aún no se han conservado en el servidor. El estado de la IU también está aumentando en complejidad, ya que necesitamos administrar rutas activas, pestañas seleccionadas, hilanderos, controles de paginación, etc.

Gestionar este estado siempre cambiante es difícil. Si un modelo puede actualizar otro modelo, una vista puede actualizar un modelo, lo que actualiza otro modelo, y esto, a su vez, puede hacer que se actualice otra vista. En algún momento, ya no comprende lo que sucede en su aplicación, ya que ha perdido el control sobre cuándo, por qué y cómo su estado. Cuando un sistema es opaco y no determinista, es difícil reproducir errores o agregar nuevas funciones.

Como si esto no fuera lo suficientemente malo, considere los nuevos requisitos que se vuelven comunes en el desarrollo de productos front-end. Como desarrolladores, se espera que manejemos las actualizaciones optimistas, la representación del lado del servidor, la obtención de datos antes de realizar las transiciones de ruta, etc. Nos encontramos tratando de manejar una complejidad con la que nunca hemos tenido que lidiar antes, e inevitablemente hacemos la pregunta: ¿es hora de rendirse? La respuesta es no.

Esta complejidad es difícil de manejar ya que estamos mezclando dos conceptos que son muy difíciles de razonar para la mente humana: la mutación y la asincronía. Los llamo Mentos y Coca-Cola. Ambos pueden ser geniales en la separación, pero juntos crean un desastre. Las bibliotecas como React intentan resolver este problema en la capa de vista eliminando tanto la asincronía como la manipulación directa del DOM. Sin embargo, administrar el estado de sus datos depende de usted. Aquí es donde entra Redux.

Siguiendo los pasos de Flux, CQRS y Event Sourcing , Redux intenta hacer que las mutaciones de estado sean predecibles imponiendo ciertas restricciones sobre cómo y cuándo pueden ocurrir actualizaciones . Estas restricciones se reflejan en los tres principios de Redux.

Alireza
fuente
¿Cómo puede ayudar Redux ? si tengo un modal para a datepicker(como componente) y ese componente se puede cargar desde varios componentes que viven en la misma página, ¿cómo sabría el datepickercomponente qué Acción enviar a redux? Esta es la esencia del problema, unir una acción en un componente con otro y NO con ningún otro componente . (tenga en cuenta que el datepickersí mismo es un componente profundo y profundo dentro del componente modal en sí)
vsync
@vsync no cree que reudx sea un único valor estático, redux en realidad puede tener objetos, matrices ... por lo que puede guardarlos como objeto o matriz o lo que sea en su tienda, puede ser mapdispatchtoprops y cada uno se guarda en una matriz de objetos como: [{name: "picker1", value: "01/01/1970"}, {name: "picker2", value: "01/01/1980"}] y luego use mapstatetoprops en padre y páselo a cada uno de los componentes o el lugar que desee, no estoy seguro de si responde a su pregunta, pero sin ver el código ... si están en grupos separados, también puede objetar con más detalles ... pero todo depende de cómo quiera agrupar ellos ..
Alireza
La pregunta no es sobre reduxqué puede almacenar, sino CÓMO pasar la acción en el fondo a lo que necesita desencadenarla. ¿Cómo sabe un componente profundo lo que EXACTAMENTE necesita ser activado? dado que en el ejemplo di un componente general que debería dispararse a un reductor específico, dependiendo del escenario, por lo que podría ser un reductor diferente ya que se puede usar un modal de selector de fecha para cualquier componente.
vsync
5

Esta es la forma en que manejé esto.
Digamos que tiene una <seleccionar> para el mes y una <seleccionar> para el día . El número de días depende del mes seleccionado.

Ambas listas son propiedad de un tercer objeto, el panel izquierdo. Ambos <select> también son hijos del leftPanel <div>
Es un juego con las devoluciones de llamada y los controladores en el componente LeftPanel .

Para probarlo, simplemente copie el código en dos archivos separados y ejecute index.html . Luego seleccione un mes y vea cómo cambia la cantidad de días.

date.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')
    );

Y el HTML para ejecutar el componente del panel izquierdo 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>
Skulas
fuente
si pudiera eliminar el 80% del código de ejemplo y aún así mantener su punto, sería lo mejor. mostrar CSS en el contexto de este hilo es irrelevante
vsync
3

Vi que la pregunta ya está respondida, pero si desea obtener más detalles, hay un total de 3 casos de comunicación entre componentes :

  • Caso 1: comunicación de padre a hijo
  • Caso 2: Comunicación de niño a padre
  • Caso 3: comunicación de componentes no relacionados (cualquier componente a cualquier componente)
Kaloyan Kosev
fuente
1

Extendiendo la respuesta de @MichaelLaCroix cuando un escenario es que los componentes no pueden comunicarse entre ningún tipo de relación padre-hijo, la documentación recomienda configurar un sistema de eventos global.

En el caso de <Filters />y <TopBar />no tiene ninguna de las relaciones anteriores, un simple emisor global podría usarse así:

componentDidMount - Suscríbete al evento

componentWillUnmount - Darse de baja del evento

Código React.js y 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() {

    }
}
tsuz
fuente
0

Existe tal posibilidad incluso si no son una relación Padre-Hijo, y eso es Flux. Hay una implementación bastante buena (para mí personalmente) para la llamada Alt.JS (con Alt-Container).

Por ejemplo, puede tener una barra lateral que depende de lo que se establece en los detalles del componente. La barra lateral de componentes está conectada con SidebarActions y SidebarStore, mientras que Details es DetailsActions y DetailsStore.

Podrías usar AltContainer así

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

{this.props.content}

Lo que mantendría las tiendas (bueno, podría usar "store" en lugar de "stores" prop). Ahora, {this.props.content} PUEDE SER Detalles dependiendo de la ruta. Digamos que / Detalles nos redirige a esa vista. Los detalles tendrían, por ejemplo, una casilla de verificación que cambiaría el elemento de la barra lateral de X a Y si se marcara.

Técnicamente no existe una relación entre ellos y sería difícil hacerlo sin flujo. PERO CON ESO es bastante fácil.

Ahora vamos a DetallesAcciones. Crearemos allí

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

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

y DetailsStore

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

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

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

Y ahora, este es el lugar donde comienza la magia.

Como puede ver, hay bindListener para SidebarActions.ComponentStatusChanged que se usará SI se usará setSiteComponent.

ahora en SidebarActions

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

Tenemos tal cosa. Despachará ese objeto en la llamada. Y se llamará si se usará setSiteComponent en la tienda (que puede usar en el componente, por ejemplo, durante onChange on Button o lo que sea)

Ahora en SidebarStore tendremos

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

Ahora aquí puedes ver que esperará a DetailsStore. Qué significa eso? más o menos significa que este método debe esperar a que DetailsStore se actualice para poder actualizarse.

tl; dr One Store está escuchando los métodos en una tienda y activará una acción desde la acción del componente, que actualizará su propia tienda.

Espero que pueda ayudarte de alguna manera.

Shiroo
fuente
0

Si desea explorar las opciones de comunicación entre componentes y siente que cada vez es más difícil, entonces puede considerar adoptar un buen patrón de diseño: Flux .

Es simplemente una colección de reglas que define cómo almacenar y mutar el estado general de la aplicación, y usar ese estado para representar componentes.

Hay muchas implementaciones Flux, y la aplicación oficial de Facebook es una de ellas. Aunque se considera el que contiene más código repetitivo, es más fácil de entender ya que la mayoría de las cosas son explícitas.

Algunas de las otras alternativas son flummox fluxxor fluxible y redux .

Kemal Dağ
fuente
0

Una vez estuve donde estás ahora, como principiante a veces te sientes fuera de lugar sobre cómo reaccionar para hacer esto. Voy a tratar de abordarlo de la misma manera que lo pienso ahora.

Los Estados son la piedra angular de la comunicación.

Por lo general, se trata de la forma en que altera los estados en este componente en su caso, señala tres componentes.

<List />: Que probablemente mostrará una lista de elementos según un filtro <Filters />: opciones de filtro que alterarán sus datos. <TopBar />: Lista de opciones.

Para orquestar toda esta interacción, necesitará un componente superior, llamémoslo Aplicación, que transmitirá acciones y datos a cada uno de estos componentes para que, por ejemplo, pueda verse así

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

Entonces, cuando setFilterse llama, afectará el elemento filtrado y volverá a representar ambos componentes ;. En caso de que esto no esté completamente claro, le hice un ejemplo con la casilla de verificación que puede verificar en un solo archivo:

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'));
Cabolanoz
fuente
0

El siguiente código me ayuda a configurar la comunicación entre dos hermanos. La configuración se realiza en su padre durante las llamadas render () y componentDidMount (). Se basa en https://reactjs.org/docs/refs-and-the-dom.html Espero que ayude.

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>
        );
    }
}
Sergei Zinovyev
fuente
Este es TypeScript, probablemente debería mencionarse en su respuesta. Buen concepto sin embargo.
serraosays
0

Curiosamente nadie mencionó mobx. La idea es similar a redux. Si tengo una pieza de datos a la que están suscritos múltiples componentes, entonces puedo usar esta información para manejar múltiples componentes.

windmaomao
fuente