こんにちは
ご質問のような、親コンポーネントの中に複数の子コンポーネントがあり、その中の1個がアクティブである(または選択されている)という場合、親の state にアクティブな子コンポーネントのインデクス(0始まり)を持たせるとうまくいきます。
この考え方で、以下、例となるコードを挙げていきます。
Parent#constructor
Parent
の this.state.active
は初期値を -1 とします。これは、どの子コンポーネントもアクティブではないことを表します。
javascript
1constructor(props) {
2 super(props);
3 this.state = { active: -1 };
4}
なお、より意味がはっきりするプロパティ名をつけるとすれば、 単にactive
ではなく、
activeChildIndex
だったり、あるいは、子がタブなのであれば
activeTabIndex
といったようにするところではありますが、以下では、active
のままで進めます。
Parent#onChangeActive
次に、this.state.active
を変更するメソッドですが、 メソッド名はonActive
ではなく、例えば
onChangeActive
あるいは、子がタブなのであれば、
onChangeTab
などとしたほうがよいでしょう。(他にもより良い名前があるかもしれません。) onActive
だと、Parentコンポーネント自体が active になったとき、という意味あいになってしまうからです。
以下、 onChangeActive
というメソッド名にしました。
javascript
1onChangeActive = active => {
2 this.setState({ active })
3}
上記のメソッドの引数 active
には、 0
以上の整数が入ってくる想定です。たとえば 0
が入ってきたときは、 先頭の子コンポーネント、つまり見た目でいうと、通常、一番上だったり、一番左にある子をアクティブにすることを表します。
Parent のrender は後で挙げます。先にChild
コンポーネントのコード例を挙げます。以下は、Child
を、個別のファイル、Child.js に作成することを想定しています。
Child.js
jsx
1import React from 'react';
2
3const Child = ({ text, onActive, active }) => (
4 <div onClick={onActive} className={active ? 'current' : ''}>
5 {text}
6 </div>
7)
8
9export default Child;
10
Child
はstateを持つ必要がなく、stateを持つ必要のないコンポーネントは、上記のように関数型コンポーネント(Functional Component)として書くとよいでしょう。
なお、上記の Child
では、 動作確認用の暫定で、text
というpropsを追加して、これを <div>
の内容として表示させるようにしました。
上記のようにChild
を作っておき、Parent
のほうで Child
を import すると、Parent
の render で、2つの子コンポーネントを配置するコードは以下のように書けます。
Parent#render
jsx
1render() {
2 return (
3 <div>
4 <Child
5 text="Child#0"
6 onActive={() => this.onChangeActive(0)}
7 active={this.state.active === 0}
8 />
9 <Child
10 text="Child#1"
11 onActive={() => this.onChangeActive(1)}
12 active={this.state.active === 1}
13 />
14 </div>
15 )
16 }
動作確認のため、以下のスタイルで、active になったChild
では、文字を赤の太字にします。
css
1.current { color: red; font-weight: bold }
上記で、Parentをマウントしてrender させると、以下のように表示されます。
「Child#0」をクリックすると、以下のように赤の太字になります。
上記の状態から続けて、「Child#1」をクリックすると、以下のようになり、アクティブなChild
が入れ替わったことが分かります。
これでとりあえず意図通りに動くようになりますが、ちょっとリファクタします。
Parent
の render で、Child
を3個追加して、計5個にしたいとします。その場合、<Child />
を5個書けばよいわけですが、それだとコードが冗長になってしまいますので、子の数を表す
const CHILDREN_COUNT = 5;
という定数を用意しておいて、Parent の render を以下のようにします。
Parent#render
jsx
1 render() {
2 return (
3 <div>
4 {[...Array(CHILDREN_COUNT)].map((_, childIndex) =>
5 <Child
6 key={childIndex}
7 text={`Child#${childIndex}`}
8 onActive={() => this.onChangeActive(childIndex)}
9 active={this.state.active === childIndex}
10 />
11 )}
12 </div>
13 )
14 }
上記で、 以下のように5個の Child
が表示されます。
CHILDREN_COUNT
を変更して、Child
の個数を変えても、アクティブになる Child
は(最大)1個です。
以上、参考になれば幸いです。
補足
React のバージョン 16.8 以降をお使いでしたら、Parentは、Hook を使って、以下のようにも書けます。
jsx
1const Parent = () => {
2 const [activeIndex, changeActive] = React.useState(-1);
3
4 return (
5 <div>
6 {[...Array(CHILDREN_COUNT)].map((_, i) =>
7 <Child
8 key={i}
9 text={`Child#${i}`}
10 onActive={() => changeActive(i)}
11 active={i === activeIndex}
12 />
13 )}
14 </div>
15 )
16}
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/22 15:19
2019/10/22 15:22