【问题标题】:How do I set state of sibling components easily in React?如何在 React 中轻松设置兄弟组件的状态?
【发布时间】:2015-06-15 13:03:10
【问题描述】:

我已经开始使用可点击列表组件来驱动选择元素。正如您从下面看到的,ListItem 中的onClick,我将子元素(在本例中为ListItem)的状态传递给父元素(SelectableListCustomSelect 组件)。这工作正常。但是,我还想做的是更改 sibling 组件(其他 ListItems)的状态,以便在单击 ListItems 之一时切换它们的选定状态。

目前,我只是使用document.querySelectorAll('ul.cs-select li) 来抓取元素并将类更改为已选中,当它与单击的ListItem 的索引不匹配时。这在一定程度上有效。然而,点击几下后,组件的状态并没有被 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;

【问题讨论】:

    标签: reactjs siblings


    【解决方案1】:

    父组件应该向子组件传递一个回调,每个子组件在其状态发生变化时都会触发该回调。实际上,您可以将所有状态保存在父级中,将其用作单个事实点,并将“选定”值作为道具传递给每个子级。

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

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

    它没有状态,它只是在单击时触发onToggle 回调。父级看起来像这样:

    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 属性中的状态向下传递给每个子级来重新渲染子级。

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

    https://jsfiddle.net/fth25erj/

    【讨论】:

    • 谢谢科林。看起来我可以完成这项工作。只需要 onChildToggle() 中的一些逻辑来使其完全选择,而不是多选选项。
    • 如果是单选,只需存储一个表示所选元素的值。这通常是所选数据项的索引或其 id。然后你可以做selected={this.state.selected === index}selected={this.state.selected === dataItem.id}
    【解决方案2】:

    兄弟姐妹交流的另一种策略是使用观察者模式。

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

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

    在 React 的上下文中,这意味着一些组件订阅接收特定消息,而其他组件向这些订阅者发布消息。

    组件通常会在 componentDidMount 方法中订阅并在 componentWillUnmount 方法中取消订阅。

    这里有 4 个实现观察者模式的库。它们之间的区别是微妙的 - EventEmitter 是最受欢迎的。

    • PubSubJS: "一个用 JavaScript 编写的基于主题的发布/订阅库。"
    • EventEmitter:“浏览器的事件 JavaScript。”它实际上是一个已经作为 nodejs 核心的一部分存在的库的实现,但用于浏览器。
    • MicroEvent.js: "事件发射器微库 - 20 行 - 用于节点和浏览器"
    • mobx:“简单、可扩展的状态管理。”

    取自:8 no-Flux strategies for React component communication,总体来说也是一本不错的读物。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-12
        • 2014-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-27
        相关资源
        最近更新 更多