2016-03-21 3 views
6

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

Во-первых, я опишу задачу:

Скажем, у меня есть несколько <select> компонентов, которые являются потомками одного родителя, который проходит вниз полей выбора динамически, состоящий из массива. Каждый ящик имеет точно такие же доступные параметры в своем начальном состоянии, но как только пользователь выбирает конкретную опцию в одном окне, ее необходимо отключить как опцию во всех остальных блоках до ее выпуска.

Вот пример того же в (глупом) коде. (Я использую react-select как стенография для создания полей выбора.)

В этом примере, мне нужно отключить (т.е. установить disabled: true) варианты «Это моя любимая» и «Это мой самый нелюбимый», когда пользователь выбирает их в одном окне выбора (и освобождает их, если пользователь отменяет их выбор).

var React = require('react'); 
 
var Select = require('react-select'); 
 

 

 

 
var AnForm = React.createClass({ 
 

 
    render: function(){ 
 

 

 
     // this.props.fruits is an array passed in that looks like: 
 
     // ['apples', 'bananas', 'cherries','watermelon','oranges'] 
 
     var selects = this.props.fruits.map(function(fruit, i) { 
 

 
      var options = [ 
 
       { value: 'first', label: 'It\'s my favorite', disabled: false }, 
 
       { value: 'second', label: 'I\'m OK with it', disabled: false }, 
 
       { value: 'third', label: 'It\'s my least favorite', disabled: false } 
 
      ]; 
 

 

 
      return (
 
       <Child fruit={fruit} key={i} options={options} /> 
 
      ); 
 
     }); 
 

 

 
     return (
 
      <div id="myFormThingy"> 
 
       {fruitSelects} 
 
      </div> 
 
     ) 
 
    } 
 

 
}); 
 

 

 
var AnChild = React.createClass({ 
 

 
    getInitialState: function() { 
 
     return { 
 
      value:'', 
 
      options: this.props.options 
 
     }; 
 
    }, 
 

 
    render: function(){ 
 

 
     function changeValue(value){ 
 
      this.setState({value:value}); 
 
     } 
 

 

 
     return (
 
      <label for={this.props.fruit}>{this.props.fruit}</label> 
 
      <Select 
 
       name={this.props.fruit} 
 
       value={this.state.value} 
 
       options={this.state.options} 
 
       onChange={changeValue.bind(this)} 
 
       placeholder="Choose one" 
 
      /> 
 
     ) 
 
    } 
 
});

обновляет ребенка возможности лучше всего достигается путем передачи данных обратно к родителю с помощью обратного вызова? Должен ли я использовать refs для доступа к дочерним компонентам в этом обратном вызове? Помогает ли редукционный редуктор?

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

Спасибо за любую помощь.

+0

Да, да и да по-другому; redux упростит использование глобальных переменных, потому что, в общем, это то, что он делает с точки зрения логики компонента. – dandavis

ответ

19

TLDR: Да, вы должны использовать подход реквизита-от-сверху-снизу и смены-от-до-сверху. Но это может стать громоздким в более крупном приложении, поэтому вы можете использовать шаблоны проектирования, такие как Flux или Redux, для снижения сложности.

Простой React подход

Реагировать компоненты получают свои "входы" в качестве реквизита; и они сообщают свой «вывод», вызывая функции, которые были переданы им в качестве реквизита. Канонический пример:

<input value={value} onChange={changeHandler}> 

Вы передаете начальное значение в одну опору; и обработчик изменений в другой опоре.

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

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

class Example extends React.Component { 

    constructor(props) { 
     super(props); 
     this.state = { 
      // keep track of what is selected in each select 
      selected: [ null, null, null ] 
     }; 
    } 

    changeValue(index, value) { 
     // update selected option 
     this.setState({ selected: this.state.selected.map((v, i) => i === index ? value : v)}) 
    } 

    getOptionList(index) { 
     // return a list of options, with anything selected in the other controls disabled 
     return this.props.options.map(({value, label}) => { 
      const selectedIndex = this.state.selected.indexOf(value); 
      const disabled = selectedIndex >= 0 && selectedIndex !== index; 
      return {value, label, disabled}; 
     }); 
    } 

    render() { 
     return (<div> 
      <Select value={this.state.selected[0]} options={this.getOptionList(0)} onChange={v => this.changeValue(0, v)} /> 
      <Select value={this.state.selected[1]} options={this.getOptionList(1)} onChange={v => this.changeValue(1, v)} /> 
      <Select value={this.state.selected[2]} options={this.getOptionList(2)} onChange={v => this.changeValue(2, v)} /> 
     </div>) 
    } 

} 

Redux

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

Пример (только некоторые ключевые части вашего приложения Redux - см реагировать-Redux документацию, как подключить их вместе, например createStore, поставщик ...):

// reducer.js 

// Your Store is made of two reducers: 
// 'dropdowns' manages the current state of your three dropdown; 
// 'options' manages the list of available options. 

const dropdowns = (state = [null, null, null], action = {}) => { 
    switch (action.type) { 
     case 'CHANGE_DROPDOWN_VALUE': 
      return state.map((v, i) => i === action.index ? action.value : v); 
     default: 
      return state; 
    } 
}; 

const options = (state = [], action = {}) => { 
    // reducer code for option list omitted for sake of simplicity 
}; 

// actionCreators.js 

export const changeDropdownValue = (index, value) => ({ 
    type: 'CHANGE_DROPDOWN_VALUE', 
    index, 
    value 
}); 

// helpers.js 

export const selectOptionsForDropdown = (state, index) => { 
    return state.options.map(({value, label}) => { 
     const selectedIndex = state.dropdowns.indexOf(value); 
     const disabled = selectedIndex >= 0 && selectedIndex !== index; 
     return {value, label, disabled}; 
    });  
}; 

// components.js 

import React from 'react'; 
import { connect } from 'react-redux'; 
import { changeDropdownValue } from './actionCreators'; 
import { selectOptionsForDropdown } from './helpers'; 
import { Select } from './myOtherComponents'; 

const mapStateToProps = (state, ownProps) => ({ 
    value: state.dropdowns[ownProps.index], 
    options: selectOptionsForDropdown(state, ownProps.index) 
}}; 

const mapDispatchToProps = (dispatch, ownProps) => ({ 
    onChange: value => dispatch(changeDropdownValue(ownProps.index, value)); 
}); 

const ConnectedSelect = connect(mapStateToProps, mapDispatchToProps)(Select); 

export const Example =() => (
    <div> 
     <ConnectedSelect index={0} /> 
     <ConnectedSelect index={1} /> 
     <ConnectedSelect index={2} /> 
    </div> 
); 

Как вы можете видеть, логика в примере Redux такой же, как и код реакции ванили. Но он не содержится в родительском компоненте, а в редукторах и вспомогательных функциях (селекторах). Вместо того, чтобы передавать реквизиты сверху вниз, React-Redux соединяет каждый отдельный компонент с состоянием, что приводит к более простому, более модульному и простому в использовании коду.

+1

Действительно полезные примеры и объяснение различий. Большое спасибо за то, что вы собрали это время. На данный момент я пошел с решением для решения ванили, которое работает как удовольствие. – JMcClure

+1

Я думаю, что в течение первых недель или двух, Redux является обязательным. Эта постоянная передача обратных вызовов везде не кажется изящным решением, хотя она и является канонической. Возможно, сейчас нужно изучить Redux. – user798719

2

Следующее поможет мне установить связь между двумя братьями и сестрами. Настройка выполняется в их родительском объекте во время вызовов render() и componentDidMount().

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> 
     ); 
    } 
} 
Смежные вопросы