Межкомпонентная связь в React

Недавно начал изучать ReactJS и столкнулся с проблемой.

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

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


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

  1. <Filter /> является дочерним компонентом <List />
  2. <Filter /> и <List /> являются дочерними компонентами в неком родительском
  3. <Filter /> и <List /> являются отдельными компонентами в корневой директории

Вариант 1

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

Ссылка на пример Межкомпонентная связь в React #1

var Filters = React.createClass({
  handleFilterChange: function(e) {
    this.props.updateFilter(e.target.value);
  },
  render: function() {
      return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="фильтр" className="form-control" />;
  }
});

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>Нет совпадений</p>
    }

    return (
        <div>
          <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Результат</h4>
        {content}
      </div>
    )
  }

})

ReactDOM.render(
  <List />,
  document.getElementById('container')
)
  • в момент события onChange поля ввода input мы вызываем метод handleFilterChange. Метод перехватывает событие на input и передает его значение в свойство updateFilter компонента <List />
  • изменение свойства updateFilter компонента <Filters /> вызывает метод handleFilterUpdate родительского компонента <List /> с переданным в него значением из input.

Вся магия кроется в свойстве updateFilter компонента <List />. Пример простой, но здесь есть несколько интересных моментов.

Вариант 2

Рассмотрим вариант, когда <Filter /> и <List /> являются дочерними компонентами в неком родительском. Он будет аналогичен первому варианту, но в этом варианте родительский компонент будет передавать передавать уже отфильтрованный список в <List />. Большой плюс этого варианта в том, что он позволяет выделить <List /> из <Filter /> в отдельные компоненты.

Ссылка на пример Межкомпонентная связь в React #2

var Filter = React.createClass({
    handleFilterChange: function(e) {
    this.props.updateFilter(e.target.value);
  },
  render: function() {
      return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="фильтр" className="form-control" />;
  }
});

var List = React.createClass({   
  render: function() {

    var content;
    console.log(this.props.items.length);
    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>Нет совпадений</p>
    }

    return (
      <div className='results'>
        <h4>Результат</h4>
        {content}
      </div>
    );
  }  
});

var ListComponent = 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>
        <Filter updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    )
  }
})

ReactDOM.render(
  <ListComponent />,
  document.getElementById('container')
)

Здесь у нас появился родительский компонент <ListComponent />, который связывает в себе компоненты <Filter /> и <List />.

  • в момент события onChange поля ввода input мы вызываем метод handleFilterChange. Метод перехватывает событие на input и передает его значение в свойство updateFilter.
  • изменение свойства updateFilter компонента <Filters /> вызывает метод handleFilterUpdate родительского компонента <ListComponent /> с переданным в него значением из input.
  • изменение состояния state компонента <ListComponent /> вызывает метод render для перерисовки компонента. А в методе render обновляется свойство itemsдочернего компонента <List />.
  • в свою очередь обновление свойства items компонента <List /> вызывет его render с новыми параметрами.

Вариант 3

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

Источник

Перевод