あるコンポーネントAを再利用して新しいコンポーネントBを作りたいとする。
- (1) 原始的な方法はソースコードをコピペして一部修正する
- (2) それより少し進んだ方法としては
- コンポーネントAをクラスとして実装し、
- 変更される部分をメソッドに括り出しておいた上で、
- クラスAを継承してクラスBを作り
- 一部のメソッドをオーバーライドする
- (3) 今回は関数コンポーネントなのでシンプルな方法としては
- 関数Bから関数Aを呼び出す
ところが厄介なことにfunc Aが状態Xを持っている、func Bの中でこの状態にアクセスしたい。 なおここでいう「状態X」とは下記の2つの対になったもの。
-
値x
-
値xを更新する関数setX useStateを呼ぶことで返り値として得られる。
-
(4) Xはfunc Aのローカル変数なのでfunc Aの外からアクセスできない。
-
(5) そこでfunc BでXを作って渡そうと考えた(伏線: ここまでは良い)
- Xをオプション引数にして、渡された時にはそれを使い、渡されなかった場合にはfunc Aが自分で作るようにした(伏線: これが間違い)
const [x, setX] = props.X ? props.X : useState(...);
- これはNGである
React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
-
(6) ならば状態Xを扱っているコードをfunc Aから括り出して func handleXとし、
- AとBからそれを使えばいいのではないかと考えた
- これはNG、なぜかというとfunc Bの中のXが更新されたからといってfunc Aの中のXは更新されない
- 個別にXを持ったのではいけない
-
(7) 状態Xを持つコンポーネントを1つにまとめるためにfunc Cを作った
- func Cはprops.childrenを使って引数として子エレメントを受け取りレンダリングする
- Composition vs Inheritance – React
- これで当初の目的は達成できた
ところで、このコンポーネントの状態Xはサーバ上のデータベースに同期したい。さてどうするか?
- func Cに同期のためのコードを置くのは変
- func Cの実際の名前は「DraggableSVGElement」だった
- ここにデータベースアクセスのコードを書くのは明らかに単一責任でない
- Lifting State Up – React
- 子コンポーネントは自分で状態を管理することを放棄して、親にそれを委譲する
- これは(5)のタイミングで思いついていたことの前半
- 後半の「自分で状態Xを管理するコンポーネント」と「状態Xの管理を親に依存するコンポーネント」を一つの関数で表現しようとしたところが間違いであった
- 部品を小さい方からボトムアップで作るとき、つい部品がそれ自体で完結するようにしたくなってしまう。
- これは「部品」を「単独でインスタンス作成可能なクラス」で作ろうとする発想に似ている。
- 今どきの実装方法はそればかりではない
- Mixinのように単独でインスタンスを作ることができず、クラスに混ぜ込まれる存在
- トレイトのように「自分が要求するメソッド」を提供してくれるトレイトと一緒になって初めてインスタンスを作れる存在
- Reactのコンポーネントは、自分自身を描画したり機能を果たしたりするために必要な「状態」を自分自身で持たない可能性がある
-
React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand
-
明日はこの気づきを元に再構成をしよう