【问题标题】:React.js - Communicating between sibling componentsReact.js - 兄弟组件之间的通信
【发布时间】:2016-07-08 17:07:00
【问题描述】:

我是 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 reducer 有帮助吗?

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

感谢您的帮助。

【问题讨论】:

  • 是的,是的,而且是不同的方式; redux 将使它更简单,就像使用全局变量一样,因为从组件逻辑的角度来看,这基本上就是它正在做的事情。

标签: javascript reactjs flux


【解决方案1】:

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

简单的 React 方法

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

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

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

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

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

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

上述方法的主要缺点是你必须从上到下传递大量信息;随着应用程序的增长,这变得难以管理。 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 代码相同。但它并不包含在父组件中,而是包含在 reducer 和 helper 函数(选择器)中。 React-Redux 不是自上而下地传递 props,而是将每个单独的组件连接到状态,从而产生更简单、更模块化、更易于维护的代码。

【讨论】:

  • 非常有用的示例和差异解释。非常感谢您花时间整理它。目前,我使用的是原版 React 解决方案,它的工作原理就像是一种享受。
  • 我认为在第一两周内,Redux 是强制性的。这种不断传递回调无处不在似乎不是一个优雅的解决方案,尽管它是规范的。现在可能需要学习 Redux。
  • 推迟学习redux太久了,但我想这次我终于要开始学习了。写得很好的答案。谢谢。 +1
  • 如果您还在 iaretiga,请考虑升级此答案以包含新的反应挂钩。
【解决方案2】:

以下帮助我设置两个兄弟姐妹之间的通信。设置是在 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”函数在哪里?
猜你喜欢
  • 2017-05-04
  • 2017-10-30
  • 1970-01-01
  • 2017-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-23
  • 1970-01-01
相关资源
最近更新 更多