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

IT技术 javascript reactjs flux
2021-04-18 03:20:11

我是 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个回答

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

简单的react方法

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

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

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

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

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

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 的上下文功能让子组件能够直接访问您的 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>
);

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

真正有用的例子和差异的解释。非常感谢您抽出时间将它放在一起。现在,我已经使用了香草 React 解决方案,它就像一种享受。
2021-06-06 03:20:11
推迟学习 redux 太久了,但我想这次我终于必须开始学习了。写的很好的答案。谢谢。+1
2021-06-12 03:20:11
我认为在第一或两周内,Redux 是强制性的。这种无处不在的不断传递回调似乎不是一个优雅的解决方案,尽管它是规范的。现在可能需要学习 Redux。
2021-06-13 03:20:11
如果您仍然在 iaretiga 附近,请考虑升级此答案以包含新的反应挂钩。
2021-06-18 03:20:11

以下帮助我设置两个兄弟姐妹之间的通信。设置是在 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>
        );
    }
}
“setMapPanel”函数在哪里?
2021-05-30 03:20:11