Reactjs 如何在React中轻松设置同级组件的状态?

Reactjs 如何在React中轻松设置同级组件的状态?,reactjs,siblings,Reactjs,Siblings,我已经有了一个可点击列表组件的开始,它将用于驱动一个select元素。从下面可以看到,onClickListItem,我将子元素的状态(ListItem,在本例中)传递给父元素(SelectableList,以及CustomSelect组件)。这很好用。但是,我还想做的是更改同级组件(其他ListItems)的状态,以便在单击其中一个ListItems时切换其选定状态 目前,我只是使用document.querySelectorAll('ul.cs-select li)获取元素,并在类与单击的列

我已经有了一个可点击列表组件的开始,它将用于驱动一个select元素。从下面可以看到,
onClick
ListItem
,我将子元素的状态(
ListItem
,在本例中)传递给父元素(
SelectableList
,以及
CustomSelect
组件)。这很好用。但是,我还想做的是更改同级组件(其他ListItems)的状态,以便在单击其中一个ListItems时切换其选定状态

目前,我只是使用
document.querySelectorAll('ul.cs-select li)
获取元素,并在类与单击的
列表项的索引不匹配时将其更改为selected。这在一定程度上是可行的。但是,单击几下之后,组件的状态并没有被React更新(仅由客户端JS更新),事情开始崩溃。我要做的是更改同级列表项的
this.state.isSelected
,并使用此状态刷新SelectableList组件。有人能提供一个更好的替代我在下面写的吗

var React = require('react');
var SelectBox = require('./select-box');

var ListItem = React.createClass({
    getInitialState: function() {
        return {
            isSelected: false
        };
    },

    toggleSelected: function () {
        if (this.state.isSelected == true) {
            this.setState({
                isSelected: false
            })
        } else {
            this.setState({
                isSelected: true
            })
        }
    },

    handleClick: function(listItem) {
        this.toggleSelected();
        this.props.onListItemChange(listItem.props.value);

        var unboundForEach = Array.prototype.forEach,
            forEach = Function.prototype.call.bind(unboundForEach);

        forEach(document.querySelectorAll('ul.cs-select li'), function (el) {

            // below is trying to 
            // make sure that when a user clicks on a list
            // item in the SelectableList, then all the *other*
            // list items get class="selected" removed.
            // this works for the first time that you move through the 
            // list clicking the other items, but then, on the second
            // pass through, starts to fail, requiring *two clicks* before the
            // list item is selected again.
            // maybe there's a better more "reactive" method of doing this?

            if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
                el.classList.remove('selected');
            }
        });
    },

    render: function() {
        return (
            <li ref={"listSel"+this.props.key}
                data-value={this.props.value}
                data-index={this.props.index}
                className={this.state.isSelected == true ? 'selected' : '' } 
                onClick={this.handleClick.bind(null, this)}>
                {this.props.content}
            </li>
        );
    }
});

var SelectableList = React.createClass({

    render: function() {

        var listItems = this.props.options.map(function(opt, index) {
            return <ListItem key={index} index={index} 
                        value={opt.value} content={opt.label}
                        onListItemChange={this.props.onListItemChange.bind(null, index)} />;
        }, this);

        return <ul className="cs-select">{ listItems }</ul>;
    }

})

var CustomSelect = React.createClass({

    getInitialState: function () {
        return {
            selectedOption: ''
        }
    },

    handleListItemChange: function(listIndex, listItem) {
        this.setState({
            selectedOption: listItem.props.value
        })
    },

    render: function () {

        var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];

        return (
            <div className="group">
                <div className="cs-select">
                    <SelectableList options={options} 
                        onListItemChange={this.handleListItemChange} />
                    <SelectBox className="cs-select" 
                        initialValue={this.state.selectedOption} 
                        fieldName="custom-select" options={options}/>
                </div>
            </div>
        )
    } 
})

module.exports = CustomSelect;
var React=require('React');
var SelectBox=require(“./SelectBox”);
var ListItem=React.createClass({
getInitialState:函数(){
返回{
结果:错
};
},
toggleSelected:函数(){
if(this.state.isSelected==true){
这是我的国家({
结果:错
})
}否则{
这是我的国家({
是的
})
}
},
handleClick:函数(listItem){
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach=Array.prototype.forEach,
forEach=Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'),函数(el){
//下面是试图
//确保当用户单击列表时
//可选择列表中的项目,然后是所有*其他*
//删除列表项get class=“selected”。
//这是您第一次通过
//单击列表中的其他项目,然后在第二个
//通过,开始失败,需要在
//再次选择列表项。
//也许有更好的更“被动”的方法?
如果(el.dataset.index!=listItem.props.index&&el.classList.contains('selected')){
el.classList.remove('selected');
}
});
},
render:function(){
返回(
  • {this.props.content}
  • ); } }); var SelectableList=React.createClass({ render:function(){ var listItems=this.props.options.map(函数(opt,索引){ 返回; },这个); return
      {listItems}
    ; } }) var CustomSelect=React.createClass({ getInitialState:函数(){ 返回{ 已选择选项:“” } }, handleListItemChange:函数(listIndex、listItem){ 这是我的国家({ selectedOption:listItem.props.value }) }, 渲染:函数(){ var选项=[{value:“One”,label:“One”},{value:“Two”,label:“Two”},{value:“Three”,label:“Three”}]; 返回( ) } }) module.exports=CustomSelect;
    父组件应该将回调传递给子组件,并且每个子组件在其状态更改时都会触发该回调。实际上,您可以在父对象中保留所有状态,将其用作单个真理点,并将“选定”值作为道具传递给每个子对象

    在这种情况下,孩子可能看起来像这样:

    var Child = React.createClass({
        onToggle: function() {
            this.props.onToggle(this.props.id, !this.props.selected);
        },
    
        render: function() {
            return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
        }
    });
    
    var Parent = React.createClass({
        getInitialState: function() {
            return {
                selections: []
            };
        },
        onChildToggle: function(id, selected) {
            var selections = this.state.selections;
    
            selections[id] = selected;
    
            this.setState({
                selections: selections
            });
        },
    
        buildChildren: function(dataItem) {
            return <Child
                id={dataItem.id}
                label={dataItem.label}
                selected={this.state.selections[dataItem.id]}
                onToggle={this.onChildToggle} />
        },
    
        render: function() {
            return <div>{this.props.data.map(this.buildChildren)}</div>
        }
    });
    
    它在状态中保存一个选择数组,当它处理来自子对象的回调时,它使用
    setState
    通过在
    selected
    prop中将其状态向下传递给每个子对象来重新渲染子对象

    您可以在此处看到一个工作示例:


    以下代码帮助我设置两个兄弟姐妹之间的通信。在render()和componentDidMount()调用期间,在其父级中完成设置

    类应用程序扩展了React.Component{
    专用导航面板:导航面板;
    private_mapPanel:mapPanel;
    构造函数(){
    超级();
    this.state={};
    }
    //ReactJS在`render()之后调用`componentDidMount()``
    componentDidMount(){
    //将地图面板传递到导航面板
    //它将允许_navigationPanel直接调用_mapPanel
    this.\u navigationPanel.setMapPanel(this.\u mapPanel);
    }
    render(){
    返回(
    //`ref=`有助于在渲染期间获取对子对象的引用
    {this._navigationPanel=child;}}/>
    {this.\u mapPanel=child;}}/>
    );
    }
    }
    
    兄弟姐妹沟通的另一种策略是使用观察者模式

    观察者模式是一种软件设计模式,其中一个对象可以向多个其他对象发送消息

    使用此策略不需要兄弟姐妹关系或父子关系

    在React的上下文中,这意味着一些组件订阅以接收特定消息,而其他组件将消息发布到这些订阅
    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>
            );
        }
    }