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

JMc*_*ure 19 javascript flux reactjs

我是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"
            />
        )
    }
});
Run Code Online (Sandbox Code Playgroud)

更新子选项是通过回调将数据传递回父级来实现的吗?我应该使用refs来访问该回调中的子组件吗?redux减速机有帮助吗?

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

谢谢你的帮助.

iar*_*iga 31

TLDR:是的,你应该使用从顶部到底部的道具和从底部到顶部的变换处理程序.但是在较大的应用程序中,这可能会变得难以处理,因此您可以使用Flux或Redux等设计模式来降低复杂性.

简单的反应方法

React组件接收其"输入"作为道具; 他们通过调用作为道具传递给他们的函数来传达他们的"输出".一个典型的例子:

<input value={value} onChange={changeHandler}>
Run Code Online (Sandbox Code Playgroud)

你在一个道具中传递初始值; 和另一个道具中的变更处理程序.

谁可以传递值并将处理程序更改为组件?只有他们的父母.(嗯,有一个例外:您可以使用上下文在组件之间共享信息,但这是一个更高级的概念,并将在下一个示例中使用.)

因此,在任何情况下,它都是您选择的父组件,应该管理您的选择的输入.这是一个例子:

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

}
Run Code Online (Sandbox Code Playgroud)

终极版

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

示例(只是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>
);
Run Code Online (Sandbox Code Playgroud)

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

  • 我想在第一周或第二周内,Redux是强制性的.这种不断传递回调的地方似乎并不是一个优雅的解决方案,尽管它是规范的.可能现在需要学习Redux. (4认同)
  • 非常有用的例子和差异的解释。非常感谢您花时间整理它。目前,我使用了普通的 React 解决方案,它的工作就像一种享受。 (2认同)

Ser*_*yev 6

以下内容可帮助我设置两个兄弟姐妹之间的通信。设置是在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>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • “setMapPanel”函数在哪里? (3认同)