web-dev-qa-db-fra.com

React.js - Communiquer entre les composants frères

Je suis nouveau dans React, et j'aimerais poser une question de stratégie sur la meilleure façon d'accomplir une tâche où les données doivent être communiquées entre les composants frères.

Je vais d'abord décrire la tâche:

Disons que j'ai plusieurs <select> composants qui sont les enfants d'un parent unique qui transmet les boîtes de sélection dynamiquement, composées d'un tableau. Chaque boîte a exactement les mêmes options disponibles dans son état initial, mais une fois qu'un utilisateur sélectionne une option particulière dans une boîte, elle doit être désactivée comme option dans toutes les autres boîtes jusqu'à ce qu'elle soit libérée.

Voici un exemple de la même chose en code (idiot). (J'utilise react-select comme raccourci pour créer les zones de sélection.)

Dans cet exemple, je dois désactiver (c'est-à-dire définir disabled: true) les options pour "C'est mon préféré" et "C'est mon moins préféré" lorsqu'un utilisateur les sélectionne dans une case de sélection (et les libère si un utilisateur les désélectionne).

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

Est-il préférable de mettre à jour les options enfant en transmettant les données au parent via un rappel? Dois-je utiliser des références pour accéder aux composants enfants dans ce rappel? Un réducteur redux aide-t-il?

Je m'excuse pour la nature générale de la question, mais je ne trouve pas beaucoup de directives sur la façon de traiter ces interactions de composants de frère à frère de manière unidirectionnelle.

Merci pour toute aide.

16
JMcClure

TLDR: Oui, vous devez utiliser une approche d'accessoires de haut en bas et de gestionnaires de changements de bas en haut. Mais cela peut devenir compliqué dans une application plus grande, vous pouvez donc utiliser des modèles de conception comme Flux ou Redux pour réduire votre complexité.

Simple React

Les composants React reçoivent leurs "entrées" comme accessoires; et ils communiquent leur "sortie" en appelant des fonctions qui leur ont été transmises comme accessoires. Un exemple canonique:

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

Vous passez la valeur initiale dans un accessoire; et un gestionnaire de changement dans un autre accessoire.

Qui peut transmettre des valeurs et modifier des gestionnaires à un composant? Seul leur parent. (Eh bien, il y a une exception: vous pouvez utiliser le contexte pour partager des informations entre les composants, mais c'est un concept plus avancé et sera exploité dans l'exemple suivant.)

Donc, dans tous les cas, c'est le composant parent de vos sélections qui doit gérer l'entrée de vos sélections. Voici un exemple:

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

Le principal inconvénient de l'approche ci-dessus est que vous devez transmettre beaucoup d'informations de haut en bas; à mesure que votre application se développe, cela devient difficile à gérer. React-Redux tire parti de la fonction de contexte de React pour permettre aux composants enfants d'accéder directement à votre boutique, simplifiant ainsi votre architecture.

Exemple (juste quelques éléments clés de votre application redux - voir la documentation react-redux comment les câbler ensemble, par exemple createStore, Provider ...):

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

Comme vous pouvez le voir, la logique de l'exemple Redux est la même que le code Vanilla React. Mais elle n'est pas contenue dans le composant parent, mais dans les réducteurs et les fonctions d'assistance (sélecteurs). au lieu de passer des accessoires de haut en bas, React-Redux connecte chaque composant individuel à l'état, résultant en un code plus simple, plus modulaire et plus facile à maintenir.

29
iaretiga

Les informations suivantes m'aident à configurer la communication entre deux frères et sœurs. La configuration est effectuée dans leur parent lors des appels de render () et 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>
        );
    }
}
5
Sergei Zinovyev