私はReactを初めて使用しますが、兄弟コンポーネント間でデータを通信する必要があるタスクをどのように達成するのが最善かについて、戦略的な質問をしたいと思います。
最初に、タスクについて説明します。
複数の<select>
コンポーネントがあり、それらが単一の親の子であり、選択ボックスを動的に渡し、配列から構成されているとします。各ボックスには初期状態でまったく同じ使用可能なオプションがありますが、ユーザーが1つのボックスで特定のオプションを選択したら、リリースされるまで他のすべてのボックスでオプションとして無効にする必要があります。
以下は、同じ(愚かな)コードの例です。 (私はreact-select
を選択ボックスを作成するための短縮形として使用しています。)
この例では、ユーザーが1つの選択ボックスでそれらを選択するときに「It's my favorite」および「It's myest Favorite」のオプションを無効にする(つまり、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"
/>
)
}
});
コールバックを介して親にデータを渡すことで、子オプションを更新するのが最善ですか? refを使用して、そのコールバックの子コンポーネントにアクセスする必要がありますか?還元剤は役立ちますか?
質問の一般的な性質をおaびしますが、これらの兄弟間コンポーネントの相互作用を単方向で処理する方法について、多くの方向性を見つけていません。
助けてくれてありがとう。
TLDR:はい、上から下への小道具と、下から上への変更ハンドラーを使用する必要があります。しかし、これは大規模なアプリケーションでは扱いにくいため、FluxやReduxなどのデザインパターンを使用して複雑さを軽減できます。
シンプルReactアプローチ
Reactコンポーネントは、「入力」を小道具として受け取ります。また、小道具として渡された関数を呼び出すことにより、「出力」を伝えます。標準的な例:
<input value={value} onChange={changeHandler}>
1つの小道具で初期値を渡します。そして別のプロップの変更ハンドラー。
誰が値を渡し、ハンドラーをコンポーネントに変更できますか?親のみ。 (まあ、例外があります。コンテキストを使用してコンポーネント間で情報を共有できますが、それはより高度な概念であり、次の例で活用されます。)
したがって、いずれにしても、selectの入力を管理するのはselectの親コンポーネントです。以下に例を示します。
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のコンテキスト機能を活用して、子コンポーネントがストアに直接アクセスできるようにして、アーキテクチャを簡素化します。
例(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の例のロジックはVanilla Reactコードと同じです。ただし、親コンポーネントではなく、レデューサーおよびヘルパー関数(セレクター)に含まれています。トップダウンでのプロップの受け渡しの代わりに、React-Reduxは個々のコンポーネントを状態に接続し、よりシンプルで、よりモジュール化された、保守しやすいコードを作成します。
以下は、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>
);
}
}