2016-03-21 11 views
6

Jestem nowy w React i chciałbym zadać pytanie dotyczące strategii, jak najlepiej wykonać zadanie, w którym dane muszą być przekazywane między komponentami siostrzanymi.React.js - Komunikacja pomiędzy komponentami rodzeństwa

Najpierw opiszę zadanie:

Say mam wiele <select> składniki, które są dzieci z jednym rodzicem, który przechodzi w dół wybierz pola dynamicznie, składający się z tablicy. Każde pole ma dokładnie takie same dostępne opcje w stanie początkowym, ale gdy użytkownik wybierze konkretną opcję w jednym polu, musi być wyłączone jako opcja we wszystkich pozostałych polach, dopóki nie zostanie zwolnione.

Oto przykład tego samego (głupiego) kodu. (Używam react-select jako skrót do tworzenia SELECT pola.)

W tym przykładzie, muszę wyłączyć (czyli ustawić disabled: true) opcje dla „To mój ulubiony” i „To mój najmniej ulubiony”, gdy użytkownik wybiera je w jednym polu wyboru (i zwalnia je, jeśli użytkownik odznaczy je).

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

aktualizuje opcje dziecko najlepiej osiągnąć, przekazując dane z powrotem do macierzystej przez zwrotnego? Czy należy używać odwołań, aby uzyskać dostęp do komponentów podrzędnych w tym wywołaniu zwrotnym? Czy reduktor redux pomaga?

Przepraszam za ogólną naturę pytania, ale nie znajduję wielu wskazówek, jak radzić sobie z tymi interakcjami między rodzeństwem i rodzeństwem w jednokierunkowy sposób.

Dzięki za pomoc.

+0

tak, tak, i tak w inny sposób; redux uprości to, tak jak przy użyciu zmiennych globalnych, ponieważ, cóż, to w zasadzie to, co robi z perspektywy logiki komponentu. – dandavis

Odpowiedz

19

TLDR: Tak, powinieneś używać rekwizytów od góry do dołu i zmieniać procedury obsługi od góry do góry. Ale to może stać się nieporęczne w większej aplikacji, więc możesz użyć wzorów takich jak Flux lub Redux, aby zmniejszyć swoją złożoność.

Proste React podejście

reagują składniki odbierać swoje "wejść" jako rekwizyty; i komunikują swój "wynik", wywołując funkcje, które zostały im przekazane jako rekwizyty. Przykład kanoniczny:

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

Podajesz wartość początkową w jednym podpórce; i modyfikator zmiany w innym podpórce.

Kto może przekazywać wartości i zmieniać procedury obsługi do komponentu? Tylko ich rodzic. (No cóż, jest wyjątek: możesz użyć kontekstu do wymiany informacji między komponentami, ale jest to bardziej zaawansowana koncepcja i zostanie zastosowana w następnym przykładzie.)

Tak czy inaczej, jest to element macierzysty swoich wybranych, które powinny zarządzać danymi wejściowymi dla wybranych. Oto przykład:

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

Główną wadą powyższego podejścia jest to, że trzeba przekazać wiele informacji od góry do dołu; wraz ze wzrostem aplikacji staje się to trudne.React-Redux wykorzystuje funkcję kontekstową React, aby umożliwić komponentom podrzędnym bezpośredni dostęp do Twojego Sklepu, co upraszcza Twoją architekturę.

Przykład (zaledwie kilka kluczowych kawałki aplikacji Redux - patrz reagować-redux dokumentacji jak drut je razem, np createstore, dostawca ...):

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

Jak widać, logika w przykładzie Redux jest taki sam jak kod waniliowy React. Ale nie jest zawarty w komponencie macierzystym, ale w reduktorach i funkcjach pomocniczych (selektorach). Zamiast przechodzenia od góry do dołu rekwizytów, React-Redux łączy każdy poszczególny komponent ze stanem, co daje prostszy, bardziej modułowy i łatwiejszy w utrzymaniu kod.

+1

Naprawdę pomocne przykłady i wyjaśnienie różnic. Bardzo dziękuję za poświęcony czas. Na razie poszedłem z waniliowym rozwiązaniem React, które działa jak smakołyk. – JMcClure

+1

Myślę, że w ciągu pierwszego tygodnia lub dwóch Redux jest obowiązkowy. To ciągłe przekazywanie zwrotne wszędzie nie wydaje się być eleganckim rozwiązaniem, choć jest kanoniczne. Może teraz trzeba uczyć się Redux. – user798719

2

Poniższe pomogą mi skonfigurować komunikację między dwojgiem rodzeństwa. Konfiguracja odbywa się w ich rodzicach podczas wywołań funkcji render() i 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> 
     ); 
    } 
}