React.js - 在兄弟组件之间进行通信

时间:2016-03-21 23:47:51

标签: javascript reactjs flux

我是React的新手,我想问一个关于如何最好地完成必须在兄弟组件之间传递数据的任务的策略问题。

首先,我将描述任务:

假设我有多个<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来访问该回调中的子组件吗? redux减速机有帮助吗?

我为问题的一般性质道歉,但我没有找到关于如何以单向方式处理这些兄弟到兄弟组件交互的很多方向。

感谢您的帮助。

2 个答案:

答案 0 :(得分:28)

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>)
    }

}

<强>终极版

上述方法的主要缺点是你必须从上到下传递大量信息;随着应用程序的增长,这变得难以管理。 React-Redux利用React的上下文功能使子组件能够直接访问您的商店,从而简化您的架构。

示例(只是redux应用程序的一些关键部分 - 请参阅react-redux文档,了解如何将这些内容连接在一起,例如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>
);

如您所见,Redux示例中的逻辑与vanilla React代码相同。但它不包含在父组件中,而是包含在reducers和helper函数(选择器)中。 React-Redux不是自上而下传递道具,而是将每个单独的组件连接到状态,从而产生更简单,更模块化,更易于维护的代码。

答案 1 :(得分:4)

以下帮助我建立两个兄弟姐妹之间的沟通。设置在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>
        );
    }
}