今回の記事は、React 師範代以外のすべての React ユーザーが対象です。React 師範代の方ならすでに知っていることばかりであると思います。
初心者の方には少し難しい部分もあるかもしれませんが、できるだけやらしく説明しますので頑張って食らいついてください。
最終的に私が言いたいのは、 React ではさほど厳格性は求められないし難しくないよ、難しいのはプログラミング言語全般で必須の概念である参照やポインタ、シャロー・ディープコピーなどだよ、 ということです。では始めましょう。
Thinking in React
React ユーザーであれば基礎の基礎である Thinking in React の教えは当然に体に染みついていると思います。
Thinking in React で述べられていることは本当に大切なことばかりで、基礎中の基礎、大原則といえることばかりです。この記事で述べることは「その大原則は間違っている!」ということではありませんので注意してください。「原則には例外があります」というのがこの記事でいいたいことです。 絶対に勘違いしないようにしてください。
Static version と Interactivity の峻別
Thinking in React の一節です。この記事をお読みいただいた後にこの一節を思い出していただけると味わい深いと思います。
Building a static version requires a lot of typing and no thinking, but adding interactivity requires a lot of thinking and not a lot of typing.
Thinking in React での説明は、Static version をまずは作成し、それに Interactivity で命を与える、みたいな流れですが、ここでは説明の都合上、Interactivity の方から説明したいと思います。
Interactivity
前回は Interactivity の一部である Side Effect 関係の話をしました。ロジックの部分の自由さについてご理解いただけていれば幸いです。
Interactivity で重要なものは前回も登場しましたリアクティブ変数の管理です。state で管理するのが基本ですが、Jotai など store で管理するのも有力であることについては前回触れました。
突然ですが、課題を出します。
「100 行 100 列の文字列を配列で管理し、それをスプレッドシートのような感じで表示するコンポーネントを作成してください。そのコンポーネントができたら、ボタンとコンポーネントを並べて表示し(並べ方は自由)、ボタンを押したら、コンポーネント内の 3 行 4 列部分にある文字列を "TEST OK!" に変更してください。」
課題の前段部分だけであれば比較的簡単であると思います。props で 100x100の配列を渡しそれを表示すればよいだけです。
では、後段部分の実現はどうでしょう。
「state にその配列を容れ、表示する際はその state を表示用に使う。ボタンを押したら state の内容を更新すればよい。今回は state はオブジェクト(配列)なわけだから React に更新を検知させるためにシャローコピーにならないよう注意が必要なことくらいは知ってるよ。具体的にいえば setState([...state])
みたいにしてあげないとね。簡単すぎる」
結論から言えば、それでも正解ではあるわけですが、その処理だけでは依然オブジェクトのシャローコピーであり、けしてオブジェクトのディープコピーまでには至っていないことは理解している必要があります。
いや、React の知識としては、「オブジェクトのシャローコピー、ディープコピーうんぬん」というよりも、「表示に変更が生じた場合は必ず再描画せよ」ということが重要なのです。
誤解をおそれずに言えば「state や store などのリアクティブな仕組みを使わなくても表示に変更が生じた場合は必ず再描画されさえすればよい」のです。これが「Interactivityの例外」です。
Interactivityの例外を深掘る
何度でも言いますが、Interactivity ではリアクティブ変数を使うのが原則です。Interactivity の例外は文字通り「例外」に過ぎないわけですが、この例外について知らないとかなりの高確率で壁にぶち当たる日が訪れます。「やっぱり React 難しい・・・」となる前に、React 初心者であってもぜひ知っておいて欲しい知識なのです。
仮に「Interactivityの例外」を許容しない頭の固い上級SEの下でさっきの課題を解く場合はどうなるでしょう。そうですね、きっちり配列の内容をディープコピーすることを求められます。まあ 100 x 100 くらいなら許容範囲かもしれませんが、描画速度に影響が見え始めてくるとその上級SEはこういうでしょう。
「そういうことをしてくれる専用コンポーネントを探してきて」
その上級SEはもちろんその「専用コンポーネント」が「Interactivityの例外」を利用しているなど夢にも思わないでしょうが。
次のようなソースが Interactivityの例外のおそらくもっとも典型的なパターンです。
const [state, setState] = useState({ data, tick: 0 }); // tick は version 管理ともいわれるものです
const handleUpdate = ev => {
const data = state.data;
data =・・・ここでデータを操作
setState(prev => ({ data, tick: prev.tick+1}));
}
return (<div>{state.data・・・}</div>);
これが許されるとなると、data を state 内に置く意味もほぼないことに気づくはずです。「state.data
と世間体を取り繕ったところでいったい何の得があるのであろうか」というような気持が、JavaScript に関する知識が豊富な人ほど湧き上がってくるはずなのです。
const [state, setState] = useState(0);
const handleUpdate = ev => {
data =・・・ここでデータを操作
setState(prev => prev+1);
}
return (<div>{data・・・}</div>);
☟キリトリセン (ここから削除します、すみません)
もはや state である必要もなく次のようなものでも十分です。いやむしろこれがスタンダートすらいえるかもしれません。
// 筆者注: useReducer を気軽に使うのはやめましょう。ここでは useState を使えば十分です。
// 詳しくはコメントを参照のこと。
// 削除するとコメントが意味不明になってしまうことと、後学のためこのまま残しておきます。
const redraw = useReducer(prev => prev + 1, 0)[1];
const handleUpdate = ev => {
data =・・・ここでデータを操作
redraw();
}
return (<div>{data・・・}</div>);
⇧キリトリセン (ここまで削除します。すみません)
React 有段者の方には上記のコード例だけで十分伝わると思いますが、React初心者・級位者の方向けに、より実践的な形と思われるコードも示しておきます。
const [state, setState] = useState({ data, tick: 0 }); // tick は version 管理ともいわれるものです
const handleUpdate = ev => {
const data = state.data;
data[2][3] = {name: "TEST OK!"};
setState(prev => ({ data, tick: prev.tick+1}));
}
// 厳密には表にすべきですが簡略化します
const list = state.data.map(item => <div>{item?.name}</div>);
return (<div>{list}</div>);
const [state, setState] = useState(0);
const handleUpdate = ev => {
data[2][3] = {name: "TEST OK!"};
setState(prev => prev+1);
}
// 厳密には表にすべきですが簡略化します
const list = data.map(item => <div>{item?.name}</div>);
return (<div>{list}</div>);
map
を通しただけでなんかマネーロンダリングされてきれいなお金に浄化された気分にはなるかもしれませんが、本質は変わりませんよね。{item.name}
は OK で、{data.name}
は NG といわれても・・・という感じになるのが普通のエンジニアの正常な感覚なのではないでしょうか。
なお、この Interactivity の例外の知識があれば、Vanilla JS 製や Svelte 製の部品なども、React と漏れなく描画を同期させさえすれば組み込めることが理解できるはずです。
Static version
Interactivity の例外をマスターすれば、残るは Static version における例外についてマスターするだけです。
Thinking in React が述べるように static version で利用する表示用の変数は props を通じて伝播(デンパ)させるのが大原則です。
props バケツリレーを避けたいなら context を使うのもよいでしょう。props が途中で変化するのであれば前回紹介した Jotai もおすすめです(props が変化するものは static version とはもはやいえないかもしれませんが説明の都合上ここにまとめさせてください)。
では、 props が絶対に変化しない定数である場合は?どうする?それも Jotai に容れる?
この場合は、例えば次のようにするのが常識といえば常識です。
// constants.js
export const constants = {
LABEL_USER_NAME: 'ユーザー名',
ERR_MSG: '・・・',
}
import {constants} from '../constants.js'
const MyComponet = props => {
return (<div>{constants.LABEL_USER_NAME}</div>)
}
JavaScript では他の言語(特にコンパイル型言語)にあるような純粋な定数を定義することができませんので、結局は「個々の開発者が定数とみなすもの」はコンポーネントのレンダー定義部分で直接使ってもよい、ということになります。
突き詰めますと、動的に変化する値(例えば URL など)であっても、React がルートコンポーネントを描画し始める前までに固定化されていれば問題ないことになります。考えてみれば React が起動する前の出来事に React が関与することは越権行為といえますから当たり前といえば当たり前のことです。
複雑な Interactivity (リアクティブ変数)の領域ですら合理性から例外的な自由が認められているのに、それよりも複雑とは言えない static version の領域で合理的な自由が認められていないというのは明らかにバランスを欠きますので、妥当なものといえます。
static version の例外についてまとめますと、
- 途中で値が変化するものは無理せず Jotai などを使うのがおススメ
- 完全な定数には constants モジュールのようなものがおススメ
ということになります。
最後に
React、柔軟で素敵ですね。
突き詰めると柔軟すぎて困るくらいですので、例外を適用する場合は、ソースを見てすぐその趣旨を理解できる程度にとどめておくのがおススメです。そして結局のところ、どこまで例外を許容すべきかはチームのポリシーなどによることになります。
まずは「お上(オカミ)」が定めたルール(律令)はきっちり守りましょう。
ルールが曖昧な場合は解釈が必要かもしれません。識者の意見に耳を傾けつつ、リサーチして解釈を固めましょう。ただし「どのように解釈にするかについてもすでに自由の領域なのかも」という意識だけは忘れないでください。
ルールの外側は自由な領域となります。なお、自由な領域には「行わない自由」もあります。囲碁や将棋でいうところの「手抜き」です(違う?)。
チームの場合はローカルルール(村の掟)を作ってメンバー全員(村人)の意思を統一する必要があります。どのように決定するかは各チームの自由ですが、リーダー(地頭)が決定するか、メンバー(農民)の多数決で決定することになると思います。なお、個人の場合でも、マイルール(家訓)は決めておくべきでしょう。いつか村に発展した場合に、村の掟の礎となるかもしれません。
いずれにせよ絶対にやってはいけないことは、意見の合わない他者を攻撃すること、「伝統だから」という理由付けだけで有無を言わさず農民の直訴を却下してしまうこと、です。
律令なのか村の掟(または家訓)なのかは微妙な部分がありますので注意しましょう。あまり解釈を押し付けすぎると仲間に嫌われます。我々仲間の最終目的は布教活動ではなく、よりよい作品を世の中に送り出すことです。
自由な領域には責任が伴います。ソースが汚いのをライブラリや他人のせいにしないようにしましょう。
ものごとにはすべてバランスがあります。「あっちがああなのにこっちがこうなのはバランス悪いな」「あっちではああいう意見を表明したなら、こっちでもこういう意見でないとおかしいなぁ」という気付く感覚は重要だなと思う今日この頃です。
歴史を振り返えば「筋を通す」人が成功している気がします。筋は真ん中にあります。バランスが大事です。偏っていては筋を見極めることはできないのです。
少しでも皆様の React Life の参考になれば幸いです。