Javascript React.js - 兄弟组件之间的通信

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36143767/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:41:37  来源:igfitidea点击:

React.js - Communicating between sibling components

javascriptreactjsflux

提问by JMcClure

I'm new to React, and I'd like to ask a strategy question about how best to accomplish a task where data must be communicated between sibling components.

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

First, I'll describe the task:

首先,我将描述任务:

Say I have multiple <select>components that are children of a single parent that passes down the select boxes dynamically, composed from an array. Each box has exactly the same available options in its initial state, but once a user selects a particular option in one box, it must be disabled as an option in all other boxes until it is released.

假设我有多个<select>组件,它们是单个父级的子级,它们动态地传递选择框,由数组组成。每个框在其初始状态下具有完全相同的可用选项,但是一旦用户在一个框中选择了特定选项,则必须将其禁用为所有其他框中的选项,直到它被释放。

Here's an example of the same in (silly) code. (I'm using react-selectas a shorthand for creating the select boxes.)

这是(愚蠢的)代码中相同的示例。(我react-select用作创建选择框的简写。)

In this example, I need to disable (ie, set disabled: true) the options for "It's my favorite" and "It's my least favorite" when a user selects them in one select box (and release them if a user de-selects them).

在这个例子中,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"
            />
        )
    }
});

Is updating the child options best accomplished by passing data back up to the parent through a callback? Should I use refs to access the child components in that callback? Does a redux reducer help?

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

I apologize for the general nature of the question, but I'm not finding a lot of direction on how to deal with these sibling-to-sibling component interactions in a unidirectional way.

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

Thanks for any help.

谢谢你的帮助。

回答by iaretiga

TLDR: Yes, you should use a props-from-top-to-bottom and change-handlers-from-bottom-to-top approach. But this can get unwieldy in a larger application, so you can use design patterns like Flux or Redux to reduce your complexity.

TLDR:是的,您应该使用 props-from-top-to-bottom 和 change-handlers-from-bottom-to-top 方法。但这在较大的应用程序中可能会变得笨拙,因此您可以使用 Flux 或 Redux 之类的设计模式来降低复杂性。

Simple React approach

简单的反应方法

React components receive their "inputs" as props; and they communicate their "output" by calling functions that were passed to them as props. A canonical example:

React 组件将它们的“输入”作为 props 接收;他们通过调用作为道具传递给他们的函数来传达他们的“输出”。一个规范的例子:

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

You pass the initial value in one prop; and a change handler in another prop.

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

Who can pass values and change handlers to a component? Only their parent. (Well, there is an exception: you can use the context to share information between components, but that's a more advanced concept, and will be leveraged in the next example.)

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

So, in any case, it's the parent component of your selects that should manage the input for your selects. Here is an example:

因此,无论如何,应该由您的选择的父组件管理您的选择的输入。下面是一个例子:

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

终极版

The main drawback of the above approach is that you have to pass a lot of information from the top to the bottom; as your application grows, this becomes difficult to manage. React-Redux leverages React's context feature to enable child components to access your Store directly, thus simplifying your architecture.

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

Example (just some key pieces of your redux application - see the react-redux documentation how to wire these together, e.g. createStore, Provider...):

示例(只是您的 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>
);

As you can see, the logic in the Redux example is the same as the vanilla React code. But it is not contained in the parent component, but in reducers and helper functions (selectors). An instead of top-down passing of props, React-Redux connects each individual component to the state, resulting in a simpler, more modular, easier-to-maintain code.

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

回答by Sergei Zinovyev

The following help me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.

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