ReactJS Два компоненти зв'язку


321

Я щойно розпочав роботу з ReactJS і трохи застряг у моїй проблемі.

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

Як я можу змусити ці 3 компоненти взаємодіяти один з одним, чи мені потрібна якась глобальна модель даних, де я можу просто внести зміни?


Чи всі три компоненти брати чи один в одному?
pgreen2

Всі вони три компоненти, я вже перевпорядкував свою програму, щоб тепер усі вони мали одного і того ж батька, який може надати їм дані.
woutr_be

4
Тут ви можете використовувати флюс або шаблон pubsub. Спираючись на документи в реагуючих документах, вони залишають дещо неоднозначне речення: "Для зв'язку між двома компонентами, які не мають стосунків батько-дитина, ви можете налаштувати власну глобальну систему подій." facebook.github.io/react/tips/…
BingeBoy

@BingeBoy має рацію Flux - це чудовий спосіб написання програм reactjs, який може вирішити проблему потоку даних, обміну даними багатьма компонентами.
Ankit Patial

4
Якщо ви не хочете , щоб потрапити в Flux або Redux, це величезна стаття на цю тему andrewhfarmer.com/component-communication
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 Не впевнений, чи відповідає це вашим вимогам, але коли у подібній ситуації колись назад, ми скористалися наступними двома функціями для сортування зв'язку між дочірнім та батьківським компонентами: - listeTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} спермаEvent: функція (eventName, парами) {$ .event.trigger (eventName, params);} Сподіваюся, що це допоможе! (вибачте, не вдалося її краще відформатувати)
5122014009

29
Для сценарію 3, чи є рекомендований підхід? Будь-які документи або приклади щодо цього, генеруючи власні синтетичні події? Я не знайшов нічого в основних документах.
pwray

1
Сценарій №2 має багато сенсу ... поки вам не доведеться ставити під загрозу дизайн (якщо тільки, макет) - тоді ви усвідомлюєте необхідність створення 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 дозволяє утримувати стан у корені вашої ієрархії компонентів і мати можливість легко вводити цей стан у дуже глибоко вкладені компоненти, без клопоту не потрібно передавати реквізити до кожного проміжного компонента.

Досі контекст був експериментальною особливістю, але новий API доступний у 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>
);

Споживач використовує схему функцій рендерінга / дітей

Перегляньте цю публікацію в блозі для отримання більш детальної інформації.

Перед React 16.3 я рекомендую використовувати реакцію-трансляцію, яка пропонує досить подібний API, і використовувати колишній контекстний API.


Портали

Використовуйте портал, коли ви хочете тримати близько двох компонентів, щоб вони спілкувалися з простими функціями, як, наприклад, у звичайного батька / дитини, але ви не хочете, щоб ці 2 компоненти мали стосунки батько / дитина в DOM, оскільки візуальних / CSS обмежень, які він має на увазі (наприклад, z-індекс, непрозорість ...).

У цьому випадку ви можете використовувати «портал». Існують різні бібліотеки реагування за допомогою порталів , які зазвичай використовуються для модалів , спливаючих вікон, підказок ...

Розглянемо наступне:

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

Це трохи схоже на портали, за винятком того, що заповнений вміст відображатиметься у визначеному вами слоті, тоді як портали, як правило, створюють новий доменний вузол (часто це діти document.body)

Перевірте бібліотеку реакцій-заповнення слотів


Автобус події

Як зазначено в документації React :

Для спілкування між двома компонентами, які не мають стосунків батько-дитина, ви можете налаштувати власну глобальну систему подій. Підпишіться на події компонента компонентDidMount (), скасуйте підписку на компонент компонент WillUnmount () та зателефонуйте setState () при отриманні події.

Є багато речей, які можна використовувати для налаштування шини подій. Ви можете просто створити масив слухачів, і при публікації події всі слухачі отримають подію. Або ви можете використовувати щось на кшталт EventEmitter або PostalJs


Флюс

Flux - це в основному автобус подій, за винятком того, що приймачі подій - це магазини. Це схоже на базову систему шин подій, за винятком того, що стан керується поза React

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

Redux - це для мене реалізація Flux, яка є найближчою до джерел пошуку подій, яка приносить користь багатьом перевагам подій, як, наприклад, можливість подорожувати часом. Він не суворо пов'язаний з React і може також використовуватися з іншими функціональними бібліотеками перегляду.

Redux розумник в відео - підручник дуже приємно , і пояснює , як вона працює всередині (це дійсно просто).


Курсори

Курсори надходять від ClojureScript / Om та широко використовуються в проектах React. Вони дозволяють керувати станом за межами React, і дозволяють безлічі компонентів мати доступ для читання / запису до тієї ж частини штату, не потребуючи нічого знати про дерево компонентів.

Існує багато реалізацій, включаючи ImmutableJS , React-cursors та Omniscient

Редагувати 2016 : Схоже, люди погоджуються, що курсори працюють добре для менших додатків, але це не дуже добре масштабує складні програми. У Om Next вже немає курсорів (хоча спочатку концепція введена в Om,)


В'яз архітектури

Архітектура Elm - це архітектура, запропонована для використання мовою Elm . Навіть якщо Elm не є ReactJS, архітектура Elm може бути виконана і в React.

Дан Абрамов, автор Redux, здійснив реалізацію архітектури Elm, використовуючи React.

І Redux, і Elm дійсно чудові і, як правило, розширюють можливості пошуку подій на передній панелі, що дозволяє налагоджувати час відключення, скасовувати / повторювати, повторювати ...

Основна відмінність Redux від Elm полягає в тому, що Elm, як правило, набагато суворіше щодо управління державою. У Elm у вас не може бути локального стану компонента або гачок кріплення / відключення, і всі зміни DOM повинні бути спровоковані глобальними змінами стану. Архітектура Elm пропонує масштабований підхід, який дозволяє обробляти ВСЕ стан усередині одного незмінного об'єкта, тоді як Redux пропонує підхід, який пропонує вам обробляти MOST стану в одному незмінному об'єкті.

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

Крім того, архітектура Elm включає більше кодових плит. Писати це не так багатослівно чи складно, але я думаю, що архітектура Elm більше підходить для мов, які набираються статично.


FRP

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

Ви можете спробувати, наприклад, Rx-React

Я думаю, що використовувати ці вкладки досить схоже на те, що пропонує мова ELM із сигналами .

CycleJS не використовує ReactJS, але використовує vdom . Він поділяє багато подібності з архітектурою Elm (але простіший у використанні в реальному житті, оскільки він дозволяє гачки vdom), і він широко використовує RxJs замість функцій, і може стати хорошим джерелом натхнення, якщо ви хочете використовувати FRP з Реагуйте. Відео CycleJs Egghead приємно зрозуміти, як це працює.


CSP

CSP (Комунікація послідовних процесів) в даний час популярний (в основному через Go / goututines і core.async / ClojureScript), але ви можете використовувати їх також у javascript з JS-CSP .

Джеймс Лонг зробив відео, де пояснив, як це можна використовувати з React.

Саги

Сага - це бекенд-концепція, що походить із світу DDD / EventSourcing / CQRS, також називається "менеджер процесів". Він популяризується проектом redux-saga , здебільшого як заміна на redux-thunk для обробки побічних ефектів (тобто дзвінків API тощо). Більшість людей наразі вважають, що це лише сервіси для побічних ефектів, але насправді йдеться про роз’єднання компонентів.

Це скоріше комплімент архітектурі Flux (або Redux), ніж абсолютно нова система зв'язку, тому що сага випромінює дії Flux наприкінці. Ідея полягає в тому, що якщо у вас є widget1 і widget2, і ви хочете, щоб вони були роз'єднані, ви не можете запускати націлювання на widget2 дії від widget1. Таким чином, ви робите віджет1 лише вогневі дії, націлені на себе, а сага - це "фоновий процес", який слухає дії віджета1 і може відправляти дії, націлені на віджет2. Сага є точкою з’єднання між двома віджетами, але віджети залишаються роз'єднаними.

Якщо вам цікаво, подивіться на мою відповідь тут


Висновок

Якщо ви хочете побачити приклад того самого маленького додатка, що використовує ці різні стилі, перевірте гілки цього сховища .

Я не знаю, що найкращий варіант у довгостроковій перспективі, але мені дуже подобається, як Flux виглядає як джерело подій.

Якщо ви не знаєте понять, що сприяють подіям , погляньте на цей дуже педагогічний блог: Перетворивши базу даних зсередини за допомогою апача Samza , її потрібно прочитати, щоб зрозуміти, чому Flux приємно (але це може стосуватися і FRP )

Я думаю, що громада погоджується з тим, що найбільш перспективною реалізацією Flux є Redux , який поступово дозволить отримати дуже продуктивний досвід розробників завдяки гарячому перезавантаженню. Можливе вражаюче livecoding ala Bret Victor's Inventing on Principle video !


2
Відмінна відповідь Seb!
Алі Гаджані

7

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

Отже, що Redux робить для вас?

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

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

Подивіться на графік нижче, щоб побачити деякі відмінності між Flux та Redux ...

Redux і Flux

Подумайте про застосування Redux у вашій програмі з самого початку, якщо вашій програмі потрібна комунікація між компонентами ...

Читання цих слів з документації Redux може бути корисним для початку:

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

Керувати цим постійно мінливим станом важко. Якщо модель може оновити іншу модель, то представлення може оновити модель, яка оновить іншу модель, а це, в свою чергу, може призвести до оновлення іншого виду. У якийсь момент ви більше не розумієте, що відбувається у вашій програмі, оскільки ви втратили контроль над тим, коли, чому та яким чином є його стан. Коли система непрозора і не детермінована, важко відтворити помилки або додати нові функції.

Як би це було недостатньо погано, подумайте, що нові вимоги стають загальними при розробці продукту. Як розробники, ми очікуємо, що ми будемо обробляти оптимістичні оновлення, рендеринг на стороні сервера, отримання даних перед виконанням маршрутних переходів тощо. Ми виявляємо, що намагаємося керувати складністю, з якою раніше ніколи не мали справу, і ми неминуче задаємося питанням: чи пора відмовлятися? Відповідь - ні.

З цією складністю важко впоратися, оскільки ми змішуємо два поняття, про які людському розуму дуже важко міркувати: мутацію та асинхронність. Я називаю їх Ментос і Кокс. Обидва можуть бути чудовими в розлуці, але разом вони створюють безлад. Бібліотеки, як React, намагаються вирішити цю проблему на рівні перегляду, видаляючи як асинхронність, так і прямі маніпуляції з DOM. Однак управління станом ваших даних залишається за вами. Сюди входить Редукс.

Слідуючи крокам Flux, CQRS та Sourcing подій , Redux намагається зробити мутації стану передбачуваними, встановивши певні обмеження щодо того, як і коли можуть відбуватися оновлення . Ці обмеження відображені в трьох принципах Redux.


Як може допомогти редукс ? якщо у мене є модаль для datepicker(як компонента), і цей компонент можна завантажувати з декількох компонентів, що живуть на одній сторінці, то як би datepickerкомпонент знав, яку дію відправити на скорочення? У цьому полягає суть проблеми, що пов'язує дію в одному компоненті bto з іншим, а НЕ будь-яким іншим компонентом . (врахуйте datepickerсам глибокий, глибокий компонент всередині самого модального компонента)
vsync

@vsync не думайте, що reudx є єдиним статичним значенням, redux насправді може мати об’єкти, масиви ... так що ви можете зберегти їх як об’єкт чи масив чи що завгодно у вашому магазині, це може бути mapdispatchtoprops і кожен збережений у масиві об'єктів як: [{ім'я: "picker1", значення: "01.01.1970"}, {ім'я: "picker2", значення: "01.01.1980"}], а потім використовувати mapstatetoprops в батьківському і передати його до кожен компонент або куди ви хочете, не впевнений, чи відповідає він на ваше запитання, але не бачачи коду ... якщо вони є в окремих групах, ви також можете заперечити більше деталей ... але все залежить від того, як ви хочете групувати їх ..
Аліреза

Питання не в тому, reduxа в чому ви можете зберігати, а ЯК передавати дію глибоко вниз до того, що потрібно її запустити. Як глибокий компонент знає, що ТОЧНО потрібно запускати? оскільки в прикладі я дав загальний компонент, який повинен спрацьовувати на конкретний редуктор, залежно від сценарію, тому він може бути різним редуктором, оскільки модуль вибору дати може використовуватися для будь-якого компонента.
vsync

5

Ось як я впорався з цим.
Скажімо, у вас є <вибір> за місяць і <вибір> за день . Кількість днів залежить від обраного місяця.

Обидва списки належать третьому об’єкту - лівій панелі. Обидва <select> також є дітьми leftPanel <div>
Це гра з зворотними викликами та обробниками в компоненті LeftPanel .

Щоб перевірити його, просто скопіюйте код у два відокремлених файли та запустіть index.html . Потім виберіть місяць і подивіться, як змінюється кількість днів.

дати.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 в контексті цього потоку, це не має значення
vsync

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) є досить хороша (для мене особисто) реалізація.

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

Ви можете використовувати тоді AltContainer, як це

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

{this.props.content}

Що б підтримувати магазини (добре, що я міг би використовувати "магазин" замість "магазинів"). Тепер {this.props.content} МОЖЕ бути деталей залежно від маршруту. Скажемо, що / Деталі переспрямовують нас до цього погляду. Деталі матимуть, наприклад, прапорець, який змінив би елемент бічної панелі з 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;
}
}

І ось тут саме починається магія.

Як ви бачите, там буде застосовано bindListener на SidebarActions.ComponentStatusChanged, який буде використано IF setSiteComponent.

тепер у SidebarActions

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

У нас таке є. Він відправить цей об’єкт за викликом. І буде викликано, якщо буде використаний setSiteComponent в магазині (який ви можете використовувати в компоненті, наприклад під час onChange на кнопці від будь-якого іншого)

Тепер у 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. Що це означає? більш-менш це означає, що цей метод потрібно дочекатися оновлення DetailsStoreto, перш ніж він може оновити себе.

tl; dr One Store прослуховує методи в магазині і запустить дію з дії компонентів, яка оновить власний магазин.

Сподіваюся, це може вам якось допомогти.


0

Якщо ви хочете вивчити варіанти спілкування між компонентами і відчуваєте, що стає все складніше і складніше, тоді ви можете розглянути можливість прийняття хорошої схеми дизайну: Flux .

Це просто набір правил, який визначає, як ви зберігаєте та мутуєте стан широкого застосування програми та використовуєте цей стан для візуалізації компонентів.

Є багато реалізацій Flux, і офіційна реалізація Facebook - одна з них. Хоча він вважається тим, що містить найбільше кодового коду, але його легше зрозуміти, оскільки більшість речей є явними.

Деякі з інших альтернатив - це flusmox fluxxor fluxible та redux .


0

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

Держави є наріжним каменем для спілкування

Зазвичай те, що зводиться до цього, полягає в тому, що ви змінюєте стани цього компонента, у вашому випадку ви вказуєте три компоненти.

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

Для того, щоб упорядкувати всю цю взаємодію, вам знадобиться вищий компонент, давайте назвемо це додаток, яке передасть дії та дані кожному з цих компонентів, наприклад, може виглядати так

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

Так що, коли setFilterйого викликають, це вплине на filteredItem та повторно виведе обидва компоненти ;. Якщо це не зовсім зрозуміло, я зробив вам приклад із прапорцем, який ви можете перевірити в одному файлі:

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 () та компонентаDidMount (). Він заснований на 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.