web-dev-qa-db-ja.com

Reactで兄弟コンポーネントの状態を簡単に設定するにはどうすればよいですか?

Select要素を駆動するのに役立つクリック可能なリストコンポーネントの始まりがあります。以下からわかるように、onClickListItem、子要素(この場合はListItem)の状態を親(SelectableList、およびCustomSelectコンポーネント)。これは正常に機能しています。ただし、siblingコンポーネント(他のListItems)の状態を変更して、ListItemsの1つがクリックされたときに選択された状態を切り替えることもできます。

現時点では、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;
26
The Pied Pipes

親コンポーネントは子にコールバックを渡す必要があり、各子は状態が変化するとそのコールバックをトリガーします。実際には、すべての状態を親で保持し、それを単一の真実のポイントとして使用し、「選択された」値を各子に小道具として渡すことができます。

その場合、子は次のようになります。

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 propで渡すことにより、子を再レンダリングします。

これの実際の例をここで見ることができます:

https://jsfiddle.net/fth25erj/

24
Colin Ramsay

兄弟兄弟通信の別の戦略は、オブザーバーパターンを使用することです。

オブザーバーパターンは、オブジェクトが複数の他のオブジェクトにメッセージを送信できるソフトウェア設計パターンです。

この戦略を使用するために兄弟や親子関係は必要ありません。

Reactのコンテキスト内では、これは特定のメッセージを受信するためにサブスクライブするコンポーネントと、それらのサブスクライバーにメッセージを発行する他のコンポーネントを意味します。

通常、コンポーネントはcomponentDidMountメソッドでサブスクライブし、componentWillUnmountメソッドでサブスクライブを解除します。

以下は、Observerパターンを実装する4つのライブラリです。それらの違いは微妙です-EventEmitterは最も人気があります。

  • PubSubJS : "JavaScriptで記述されたトピックベースのパブリッシュ/サブスクライブライブラリ。"
  • EventEmitter : "ブラウザ用のイベントJavaScript。"実際には、nodejsコアの一部として既に存在するライブラリの実装ですが、ブラウザ用です。
  • MicroEvent.js :「イベントエミッターマイクロライブラリ-20行-ノードおよびブラウザー用」
  • mobx : "シンプルでスケーラブルな状態管理。"

から取得: Reactコンポーネント通信 のための8フラックスなし戦略)==これも一般的に素晴らしい読み物です。

1
Mrchief

次のコードは、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>
        );
    }
}
0
Sergei Zinovyev