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

268

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

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

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

  • 0
    Все три родственных компонента или один внутри другого?
  • 0
    Они все три компонента, я уже перестроил свое приложение так, чтобы у них теперь был один родитель, который может предоставить им данные.
Показать ещё 7 комментариев
Теги:

11 ответов

301
Лучший ответ

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

  • <Filters /> является дочерним компонентом <List />
  • Оба <Filters /> и <List /> являются дочерними элементами родительского компонента
  • <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

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

  • 0
    Спасибо, сценарий № 2 имеет большой смысл, и, подумав вчера, я реструктурировал свое приложение так, чтобы оно работало именно так. Это немного странно, поскольку все компоненты теперь зависят друг от друга. Но похоже, что делает работу.
  • 5
    Хорошая вещь с # 2 состоит в том, что они полагаются только на родителя, который передает реквизит каждому компоненту: функцию в качестве updateFilter для <Filters /> и массив в качестве items для <List /> . Вы можете использовать эти дочерние компоненты у других родителей с другим поведением, вместе или в одиночку. Например, если вы хотите отобразить динамический список, но не нуждаетесь в фильтрации.
Показать ещё 12 комментариев
120

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

реагировать

Прямая связь с родителями/детьми

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), а все дочерние 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>
);

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

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

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


Порталы

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

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

Рассмотрим следующее:

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

Не reactAppContainer создать следующую 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)

Проверьте библиотеку заполняющих закладок


Шина событий

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

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

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


Flux

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

Исходная реализация Flux выглядит как попытка сделать Event-sourcing взломанным способом.

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

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


курсоры

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

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

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


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

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

Дэн Абрамов, автор "Редукса", реализовал архитектуру "Вяз", используя "Реакт".

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

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

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

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


FRP

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

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

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

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


СНТ

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

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

Саги

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

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

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


Вывод

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

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

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

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

  • 0
    Отличный ответ, Себ!
6

Так я справлялся с этим.
Скажем, у вас есть <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>
  • 0
    если бы вы могли удалить 80% кода примера и при этом сохранить свою точку зрения, было бы лучше. показ CSS в контексте этой темы не имеет значения
5

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

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

Расширение ответа @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() {

    }
}
1

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

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

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

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

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

Изображение 2695

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

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

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

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

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

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

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

  • 0
    Как может помочь редукс ? если у меня есть модал для средства datepicker (как компонента), и этот компонент может быть загружен из нескольких компонентов, находящихся на одной странице, то как компонент datepicker узнает, какое действие необходимо отправить в редукцию? Это суть проблемы, связывая действие в одном компоненте с другим, а НЕ с любым другим компонентом . (принять во внимание, что сам datepicker является глубоким, глубоким компонентом внутри самого модального компонента)
  • 0
    @vsync не думает, что reudx - это одно статическое значение, на самом деле, при редуксе могут быть объекты, массивы ... так что вы можете сохранить их как объект или массив или что угодно в вашем хранилище, это могут быть mapdispatchtoprops и каждое из них будет сохранено в массиве объектов например: [{name: «picker1», значение: «01/01/1970»}, {name: «picker2», значение: «01/01/1980»}], а затем используйте mapstatetoprops в parent и передайте его каждый компонент или где вы хотите, не уверен, что он отвечает на ваш вопрос, но не видя код ... если они находятся в отдельных группах, вы также можете возражать с более подробной информацией ... но все зависит от того, как вы хотите группировать их..
Показать ещё 1 комментарий
0

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

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>
        );
    }
}
  • 0
    Это TypeScript, вероятно, следует упомянуть в вашем ответе. Хорошая концепция, хотя.
0

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

Государства являются краеугольным камнем для связи

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

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

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

<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

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

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

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

Некоторые другие альтернативы flummox fluxxor fluxible и redux.

0

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

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

Вы можете использовать тогда AltContainer, как это

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

{this.props.content}

Что бы сохранить магазины (ну, я могу использовать "store" вместо "store" prop). Теперь, {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;
}
}

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

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

теперь в SidebarActions

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

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

Теперь в 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;
}

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

TL;DR Один магазин прослушивает методы в магазине и запускает действие из действия компонента, которое будет обновлять собственный магазин.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню