ReactJS Два компонента общения


321

Я только начал работать с ReactJS и немного застрял на проблеме, которая у меня есть.

Мое приложение, по сути, представляет собой список с фильтрами и кнопкой для изменения макета. На данный момент я использую три компонента: <list />, < Filters />и <TopBar />, теперь , очевидно , когда я изменить настройки < Filters />я хочу , чтобы вызвать какой - либо метод в <list />обновить мой взгляд.

Как я могу заставить эти 3 компонента взаимодействовать друг с другом или мне нужна какая-то глобальная модель данных, в которую я могу просто внести изменения?


Все три родственных компонента или один внутри другого?
pgreen2

Они все три компонента, я уже перестроил свое приложение так, чтобы у них теперь был один и тот же родитель, который может предоставить им данные.
woutr_be

4
Здесь вы можете использовать шаблон потока или pubsub. Основываясь на документах в реактивных документах, они оставляют несколько двусмысленное предложение: «Для связи между двумя компонентами, которые не имеют отношения родитель-потомок, вы можете настроить собственную глобальную систему событий». facebook.github.io/react/tips/…
BingeBoy

@BingeBoy - это правильно. Flux - отличный способ создания приложений реагирования, которые могут решить проблему потока данных, совместного использования данных многими компонентами.
Анкит Патиал

4
Если вы не хотите переходить на Flux или Redux, это отличная
garajo

Ответы:


318

Лучший подход будет зависеть от того, как вы планируете расположить эти компоненты. Несколько примеров сценариев, которые приходят на ум прямо сейчас:

  1. <Filters /> является дочерним компонентом <List />
  2. Оба <Filters />и <List />являются потомками родительского компонента
  3. <Filters />и <List />жить в отдельных корневых компонентах полностью.

Могут быть и другие сценарии, о которых я не думаю. Если ваш не вписывается в эти, то дайте мне знать. Вот несколько очень грубых примеров того, как я справлялся с первыми двумя сценариями:

Сценарий № 1

Вы можете передать обработчик из <List />в <Filters />, который затем может быть вызван для onChangeсобытия, чтобы отфильтровать список с текущим значением.

JSFiddle для # 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);

Сценарий № 2

Аналогично сценарию № 1, но родительский компонент будет тем <Filters />, который передаст функцию-обработчик , и передаст отфильтрованный список <List />. Мне нравится этот метод лучше, так как он отделяет <List />от <Filters />.

JSFiddle для # 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);

Сценарий № 3

Когда компоненты не могут обмениваться данными между родителями и потомками, в документации рекомендуется настроить глобальную систему событий .


6
Хорошая вещь с # 2 является то , что они полагаются только на родителя , который проходит опору для каждого компонента: функции , как updateFilterв <Filters />и массив как itemsв <List />. Вы можете использовать эти дочерние компоненты у других родителей с другим поведением, вместе или в одиночку. Например, если вы хотите отобразить динамический список, но не нуждаетесь в фильтрации.
Майкл Лакруа

2
@woutr_be Не уверен, что это будет соответствовать вашему требованию, но когда-то в подобной ситуации, мы использовали следующие две функции для сортировки взаимодействия между дочерним и родительским компонентами: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Надеюсь, это поможет! (извините, не могу отформатировать его лучше)
5122014009

29
Для сценария 3 есть ли рекомендуемый подход? Есть какие-нибудь документы или примеры по этому вопросу, создавая собственные синтетические события? Я не нашел ничего в основных документах.
pwray

1
Сценарий № 2 имеет большой смысл ... пока вы не подвергнете опасности дизайн (если только Layout) - тогда вы осознаете необходимость в EventHub / PubSub.
Коди

4
Ссылка на сценарий № 3 устарела и теперь перенаправляет на несвязанную страницу документации React.
beporter

170

Существует несколько способов взаимодействия компонентов. Некоторые из них могут подойти для вашего использования. Вот список некоторых, которые я нашел полезным знать.

реагировать

Прямое общение родителей и детей

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

Здесь дочерний компонент будет вызывать обратный вызов, предоставленный родителем со значением, и родитель сможет получить значение, предоставленное дочерними элементами в родительском элементе.

Если вы создаете функцию / страницу своего приложения, лучше иметь одного родителя, управляющего обратными вызовами / состоянием (также называемого containerили smart component), и все дочерние элементы должны быть без состояний, только сообщая обо всем родителю. Таким образом, вы можете легко «поделиться» состоянием родителя с любым ребенком, которому это нужно.


контекст

React Context позволяет хранить состояние в корне иерархии компонентов и иметь возможность легко внедрять это состояние в очень глубоко вложенные компоненты без необходимости передавать реквизиты каждому промежуточному компоненту.

До сих пор контекст был экспериментальной функцией, но в React 16.3 доступен новый API.

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

Потребитель использует шаблон функции render prop / children

Проверьте этот блог для более подробной информации.

Перед React 16.3 я бы рекомендовал использовать реакцию-трансляцию, которая предлагает довольно похожий API, и использовать прежний контекстный API.


Порталы

Используйте портал, когда вы хотите, чтобы два компонента были расположены близко друг к другу, чтобы заставить их взаимодействовать с простыми функциями, как в обычном родительском / дочернем элементе, но вы не хотите, чтобы эти два компонента имели отношения родительский / дочерний в DOM, потому что из видимых / CSS ограничений, которые это подразумевает (например, z-index, opacity ...).

В этом случае вы можете использовать «портал». Существуют разные библиотеки реагирования, использующие порталы , обычно используемые для модальных окон, всплывающих окон, всплывающих подсказок ...

Учтите следующее:

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

Может создать следующий DOM при визуализации внутри reactAppContainer:

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

Подробнее здесь


игровые автоматы

Вы определяете слот где-то, а затем заполняете слот из другого места вашего дерева визуализации

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>

Это немного похоже на порталы, за исключением того, что заполненное содержимое будет отображаться в определенном вами слоте, в то время как порталы обычно визуализируют новый узел dom (часто дочерний элемент document.body)

Проверьте библиотеку response-slot-fill


Шина событий

Как указано в документации React :

Для связи между двумя компонентами, которые не имеют отношения родитель-потомок, вы можете настроить собственную глобальную систему событий. Подписаться на события в componentDidMount (), отписаться в componentWillUnmount () и вызвать setState () при получении события.

Есть много вещей, которые вы можете использовать для настройки шины событий. Вы можете просто создать массив слушателей, и при публикации события все слушатели получат событие. Или вы можете использовать что - то вроде EventEmitter или PostalJs


Flux

Flux - это, по сути, шина событий, за исключением того, что получатели событий являются магазинами. Это похоже на базовую систему шины событий, за исключением того, что состояние управляется вне React

Оригинальная реализация Flux выглядит как попытка хакерской работы с источником событий.

Redux для меня - это реализация Flux, наиболее близкая к источникам событий, которая обладает многими преимуществами источников событий, такими как возможность путешествовать во времени. Он не связан строго с React и может также использоваться с другими библиотеками функциональных представлений.

Видео-учебник Egghead Redux действительно хорош и объясняет, как он работает внутри (это действительно просто).


курсоры

Курсоры взяты из ClojureScript / Om и широко используются в проектах React. Они позволяют управлять состоянием вне React, и позволяют нескольким компонентам иметь доступ на чтение / запись к одной и той же части состояния без необходимости что-либо знать о дереве компонентов.

Существует множество реализаций, в том числе ImmutableJS , React-курсоры и Omniscient

Edit 2016 : кажется, что люди соглашаются, что курсоры работают хорошо для небольших приложений, но это плохо масштабируется в сложных приложениях. Om Next больше не имеет курсоров (хотя Om изначально ввел эту концепцию)


Архитектура вяза

Архитектура вяза - это архитектура, предложенная для языка вяза . Даже если Elm не ReactJS, архитектура Elm также может быть реализована в React.

Дэн Абрамов, автор Redux, реализовал архитектуру Elm с использованием React.

И Redux, и Elm действительно великолепны и имеют тенденцию расширять возможности поиска событий на внешнем интерфейсе, позволяя отлаживать путешествия во времени, отменять / возвращать, воспроизводить ...

Основное различие между Redux и Elm состоит в том, что Elm, как правило, более строг в управлении государством. В Elm вы не можете иметь локальное состояние компонента или перехватывать / демонтировать хуки, и все изменения DOM должны быть вызваны глобальными изменениями состояния. Архитектура Elm предлагает масштабируемый подход, который позволяет обрабатывать ВСЕ состояние внутри одного неизменного объекта, в то время как Redux предлагает подход, который предлагает вам обрабатывать МОСТ состояния в одном неизменном объекте.

Несмотря на то, что концептуальная модель Elm очень элегантна, а архитектура позволяет хорошо масштабироваться для больших приложений, на практике это может быть затруднено или потребовать дополнительных шаблонов для решения простых задач, таких как выделение фокуса для ввода после его монтирования или интеграция с существующей библиотекой. с императивным интерфейсом (например, плагин JQuery). Связанная проблема .

Кроме того, архитектура Elm включает в себя больше кода кода. Это не настолько многословно или сложно писать, но я думаю, что архитектура Elm больше подходит для языков со статической типизацией.


FRP

Такие библиотеки, как RxJS, BaconJS или Kefir, могут использоваться для создания потоков FRP для обработки связи между компонентами.

Вы можете попробовать, например, Rx-React

Я думаю, что использование этих библиотек очень похоже на использование языка ELM с сигналами .

Платформа CycleJS не использует ReactJS, но использует vdom . Он имеет много общего с архитектурой Elm (но его проще использовать в реальной жизни, поскольку он позволяет использовать VDOM-хуки), и он широко использует RxJ вместо функций и может стать хорошим источником вдохновения, если вы хотите использовать FRP с React. CycleJs Egghead видео приятно понять, как это работает.


СНТ

CSP (передача последовательных процессов) в настоящее время популярны (в основном из-за Go / goroutines и core.async / ClojureScript), но вы можете использовать их также в javascript с JS-CSP .

Джеймс Лонг снял видео, объясняющее, как его можно использовать с React.

Саги

Сага - это бэкэнд-концепция из мира DDD / EventSourcing / CQRS, также называемая «диспетчер процессов». Он популяризируется проектом redux-saga , в основном в качестве замены для redux-thunk для обработки побочных эффектов (например, вызовов API и т. Д.). Большинство людей в настоящее время думают, что это только услуги для побочных эффектов, но на самом деле это больше касается разъединения компонентов.

Это скорее комплимент для архитектуры Flux (или Redux), чем для совершенно новой системы связи, потому что сага в конце выдает действия Flux. Идея состоит в том, что если у вас есть widget1 и widget2, и вы хотите, чтобы они были развязаны, вы не можете запустить действие таргетинга widget2 из widget1. Таким образом, вы делаете widget1 только действия, которые нацелены на себя, и сага является «фоновым процессом», который прослушивает действия widget1 и может отправлять действия, которые нацелены на widget2. Сага - это точка соединения между двумя виджетами, но виджеты остаются разъединенными.

Если вам интересно, посмотрите мой ответ здесь


Вывод

Если вы хотите увидеть пример того же маленького приложения, использующего эти разные стили, проверьте ветки этого хранилища .

Я не знаю, какой вариант лучше в долгосрочной перспективе, но мне действительно нравится, как Flux выглядит как источник событий.

Если вы не знакомы с концепциями источников событий, взгляните на этот очень педагогический блог: выверните базу данных с помощью Apache Samza , это необходимо прочитать, чтобы понять, почему Flux хорош (но это может относиться и к FRP). )

Я думаю, что сообщество соглашается с тем, что наиболее многообещающей реализацией Flux является Redux , которая будет постепенно обеспечивать очень продуктивный опыт разработчиков благодаря горячей перезагрузке. Впечатляющее живое кодирование аля видео « Изобретая по принципу» Брета Виктора, возможно!


2
Отличный ответ, Себ!
Али Гаджани

7

Хорошо, есть несколько способов сделать это, но я исключительно хочу сосредоточиться на использовании магазина с использованием Redux, который делает вашу жизнь намного проще в этих ситуациях, а не дает вам быстрое решение только для этого случая, использование чистого React приведет к путанице в действительно большое приложение и общение между Компонентами становится все сложнее и сложнее с ростом приложения ...

Так что Redux делает для вас?

Redux - это как локальное хранилище в вашем приложении, которое можно использовать всякий раз, когда вам нужно, чтобы данные использовались в разных местах вашего приложения ...

По сути, идея Redux изначально пришла из потока, но с некоторыми фундаментальными изменениями, включая концепцию наличия единого источника правды путем создания только одного магазина ...

Посмотрите на график ниже, чтобы увидеть некоторые различия между Flux и Redux ...

Redux и Flux

Рассмотрите возможность применения Redux в вашем приложении с самого начала, если ваше приложение нуждается в связи между компонентами ...

Также для начала полезно прочитать эти слова из документации Redux:

Поскольку требования к одностраничным приложениям JavaScript становятся все более сложными, наш код должен управлять большим количеством состояний, чем когда-либо прежде . Это состояние может включать ответы сервера и кэшированные данные, а также локально созданные данные, которые еще не были сохранены на сервере. Состояние пользовательского интерфейса также усложняется, поскольку нам нужно управлять активными маршрутами, выбранными вкладками, счетчиками, элементами управления разбиением на страницы и так далее.

Управлять этим постоянно меняющимся состоянием сложно. Если модель может обновить другую модель, то представление может обновить модель, которая обновит другую модель, и это, в свою очередь, может привести к обновлению другого представления. В какой-то момент вы больше не понимаете, что происходит в вашем приложении, поскольку вы потеряли контроль над тем, когда, почему и как его состояние. Когда система непрозрачна и недетерминирована, трудно воспроизвести ошибки или добавить новые функции.

Как будто это не было достаточно плохо, рассмотрите новые требования, которые становятся общими в разработке переднего продукта. Как разработчики, от нас ожидают оптимистичные обновления, рендеринг на стороне сервера, выборка данных перед выполнением переходов по маршруту и ​​так далее. Мы пытаемся справиться со сложностью, с которой нам никогда не приходилось сталкиваться прежде, и мы неизбежно задаем вопрос: пора ли сдаваться? Ответ - нет.

С этой сложностью трудно справиться, поскольку мы смешиваем две концепции, которые человеческому разуму очень трудно рассуждать: мутация и асинхронность. Я называю их Ментос и Кола. Оба могут быть хорошими в разделении, но вместе они создают беспорядок. Такие библиотеки, как React, пытаются решить эту проблему на уровне представления, удаляя как асинхронность, так и прямые манипуляции с DOM. Однако управление состоянием ваших данных остается за вами. Это где Redux входит.

Следуя инструкциям Flux, CQRS и Event Sourcing , Redux пытается сделать изменения состояния предсказуемыми, налагая определенные ограничения на то, как и когда могут происходить обновления . Эти ограничения отражены в трех принципах Redux.


Как может помочь редукс ? если у меня есть модал для datepicker(в качестве компонента), и этот компонент может быть загружен из нескольких компонентов, находящихся на одной странице, то как datepickerкомпонент узнает, какое действие следует отправить в редукцию? В этом суть проблемы, связывая действие в одном компоненте с другим, а НЕ с любым другим компонентом . (принять во внимание, что datepickerсам по себе является глубоким, глубоким компонентом внутри самого модального компонента)
vsync

@vsync не думает, что reudx - это одно статическое значение, на самом деле, при редуксе могут быть объекты, массивы ... так что вы можете сохранить их как объект или массив или что угодно в вашем хранилище, это могут быть mapdispatchtoprops и каждое из них будет сохранено в массиве объектов например: [{name: «picker1», значение: «01/01/1970»}, {name: «picker2», значение: «01/01/1980»}], а затем использовать mapstatetoprops в родительском и передать его каждый компонент или где вы хотите, не уверен, что он отвечает на ваш вопрос, но не видя код ... если они находятся в отдельных группах, вы также можете возражать с более подробной информацией ... но все зависит от того, как вы хотите группировать их ..
Алиреза

Речь идет не о том, reduxи что вы можете хранить, но КАК пройти действие глубоко вниз к тому , что нужно , чтобы вызвать его. Как глубоко компонент знает , что ТОЧНО должен быть вызван? поскольку в примере я дал общий компонент, который должен запускаться для конкретного редуктора, в зависимости от сценария, поэтому он может быть другим редуктором, поскольку модальный модуль выбора даты можно использовать для любого компонента.
vsync

5

Это способ, которым я справился с этим.
Допустим, у вас есть <select> для месяца и <select> для дня . Количество дней зависит от выбранного месяца.

Оба списка принадлежат третьему объекту, левой панели. Оба <select> также являются потомками leftPanel <div>
Это игра с обратными вызовами и обработчиками в компоненте LeftPanel .

Чтобы проверить это, просто скопируйте код в два отдельных файла и запустите index.html . Затем выберите месяц и посмотрите, как меняется количество дней.

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

И HTML для запуска левой панели компонента 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>

если бы вы могли удалить 80% кода примера и при этом сохранить свою точку зрения, было бы лучше. показ CSS в контексте этой
темы

3

Я видел, что на этот вопрос уже дан ответ, но если вы хотите узнать больше подробностей, существует всего 3 случая связи между компонентами :

  • Случай 1: общение между родителями и детьми
  • Случай 2: общение ребенка с родителем
  • Случай 3: Связь не связанных компонентов (любой компонент с любым компонентом)

1

Расширяя ответ @MichaelLaCroix, когда сценарий состоит в том, что компоненты не могут взаимодействовать между какими-либо отношениями родитель-потомок, в документации рекомендуется настроить глобальную систему событий.

В случае <Filters />и <TopBar />не имеет какого - либо из вышеперечисленных отношений, простой глобальный эмиттер может быть использован , как это:

componentDidMount - Подписаться на событие

componentWillUnmount - Отписаться от события

Код React.js и 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

Есть такая возможность, даже если они не являются отношениями Родитель - Ребенок - и это Flux. Для этого есть довольно хорошая (лично для меня) реализация, которая называется Alt.JS (с Alt-Container).

Например, у вас может быть боковая панель, которая зависит от того, что установлено в компоненте Details. Боковая панель компонента связана с SidebarActions и SidebarStore, а Details - это DetailsActions и DetailsStore.

Тогда вы можете использовать AltContainer

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

{this.props.content}

Который будет хранить магазины (ну, я мог бы использовать «магазин» вместо «магазины» проп). Теперь {this.props.content} МОЖЕТ БЫТЬ Подробности в зависимости от маршрута. Допустим, что / Details перенаправляет нас к этому представлению. Детали будут иметь, например, флажок, который изменит элемент боковой панели с X на Y, если он будет отмечен.

Технически между ними нет никаких отношений, и было бы трудно обойтись без изменений. Но с этим это довольно легко.

Теперь давайте перейдем к DetailsActions. Мы создадим там

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

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

и DetailsStore

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

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

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

И теперь это место, где начинается магия.

Как вы можете видеть, для SidebarActions имеется bindListener.ComponentStatusChanged, который будет использоваться, если будет использоваться setSiteComponent.

сейчас в SidebarActions

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

У нас есть такая вещь. Он отправит этот объект по вызову. И он будет вызываться, если будет использоваться setSiteComponent в хранилище (которое вы можете использовать в компоненте, например, во время onChange для Button или любого другого)

Теперь в SidebarStore у нас будет

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

Теперь здесь видно, что он будет ждать DetailsStore. Что это означает? более или менее это означает, что этот метод должен ждать обновления DetailsStore, прежде чем он сможет обновиться сам.

tl; dr One Store прослушивает методы в хранилище и инициирует действие из действия компонента, которое обновит свое собственное хранилище.

Я надеюсь, что это может помочь вам как-то.


0

Если вы хотите изучить варианты взаимодействия между компонентами и почувствовать, что все сложнее и сложнее, то вы можете рассмотреть возможность использования хорошего шаблона проектирования: Flux .

Это просто набор правил, которые определяют, как вы храните и изменяете состояние приложения и используете его для визуализации компонентов.

Существует множество реализаций Flux, и официальная реализация Facebook является одной из них. Хотя это считается тот, который содержит большую часть стандартного кода, но это легче понять, так как большинство вещей явно.

Некоторые из других альтернатив смущать fluxxor плавкие и перевождите .


0

Я когда-то был там, где ты сейчас, как новичок, ты иногда чувствуешь неуместно, как реагируют на это. Я попытаюсь решить то же самое, что и сейчас.

Штаты являются краеугольным камнем общения

Обычно все сводится к тому, как вы изменяете состояния в этом компоненте, в вашем случае вы указываете на три компонента.

<List />: Который, вероятно, будет отображать список элементов в зависимости от фильтра <Filters />: параметры фильтра, которые изменят ваши данные. <TopBar />: Список опций.

Для организации всего этого взаимодействия вам понадобится более высокий компонент, назовем его App, который будет передавать действия и данные каждому из этих компонентов, так что, например, может выглядеть так

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

Поэтому, когда setFilterвызывается, это повлияет на FilterItem и повторно отрендерить оба компонента; В случае, если это не совсем понятно, я привел вам пример с флажком, который вы можете проверить в одном файле:

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

Следующий код помогает мне установить связь между двумя братьями и сестрами. Настройка выполняется в родительском объекте во время вызовов render () и componentDidMount (). Он основан на https://reactjs.org/docs/refs-and-the-dom.html Надеюсь, что это поможет.

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

Это TypeScript, вероятно, следует упомянуть в вашем ответе. Хорошая концепция, хотя.
serraosays

0

Странно, никто не упомянул mobx. Идея похожа на redux. Если у меня есть часть данных, на которую подписано несколько компонентов, я могу использовать эти данные для управления несколькими компонентами.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.