今や、ReactはJavaScriptの中心的な地位を占めるようになったと感じるときがしばしばあります。TypeScriptのように、さらに新しいプログラミング技法は生まれてきていますが、基本の考え方はReactです。
それに対して従来のJavaScriptはプログラミングそのものの考え方が違い、別の言語のように感じます。そのためReactを使いながら従前のJavaScriptのようなプログラミングをすると、「それは「Reactの方法」("The React Way")に沿ったものではありません。」と言われてしまいます。
そもそも、"The React Way"とはなんでしょうか。最近、その意味が少しわかった気がしますので、このブログにまとめておきたいと思います。
なお、従前のJavaScriptのコードはjQueryを導入していることを前提とさせていただきます。
DOM要素は基本となる情報をもとにレンダリングされる
次の図をご覧ください。
従前のJavaScriptは、DOM要素があって、それを抽出してその要素が持っているテキストや属性の情報を書き換えて再レンダリングしていました。
これに対してReactでは、まずDOM要素をレンダリングするための基礎情報というのが存在します。その情報を元にJSXという記法でDOM要素を生成し、レンダリングします。更新するのはDOM要素ではなく、基礎情報の方なのです。JSXというのはHTMLにJavaScriptの変数や条件文を記述できる記法で、HTMLに変数が埋め込まれたような記述になります。
従前のJavaScriptの記法を”imperative”、日本語でいうと「命令的」といい、Reactを"declarative"、日本語でいうと「宣言的」と言ったりもするようです。
実際のコードを比較
実際のプログラミングの場面では”imperative”と"declarative"では、レンダリング結果は同じでも、コードの違いは大きいです。
ここで誤解がないように説明しますが、Reactでも”imperative”な記法は可能だということです。”imperative”と"declarative"はあくまで記法の問題で、言語の仕様を縛っているものではありません。
そのため、今回はより違いが明確になるように、Reactで”imperative”なアプローチと"declarative"なアプローチのコードを示します。
”imperative”なアプローチ
まず、”imperative”なコードを示します。
export function NomalSelect() {
//オプションの一覧を開く
const openClick = (event) => {
const element = event.currentTarget;//⓶
if (element.classList.contains('open')) {//⓷
element.classList.remove('open');
} else {
element.classList.add('open');
}
}
return (
<div onClick={openClick}>//⓵
クリック
</div>
);
}
①はJSXでDOM要素にイベントリスナーをセットしています。
そしてイベントが発生するとopenClick
ハンドラが発火します。
②でeventオブジェクトからクリックされたオブジェクトを取得します。
③以下でJavaScriptのaddメソッド、removeメソッドでクラスをつけたり外したりしています。
このプログラミングは、DOM要素を取得し、それを直接操作して再レンダリングを起こすというまさに”imperative”なアプローチです。
"declarative"なアプローチ
次に、”declarative”なコードを示します。
export function NomalSelect() {
// open状態を管理するstate
const [isOpen, setIsOpen] = useState(false);//⓷
//オプションの一覧を開く
const openClick = () => {//⓸
// isOpenの値をトグルする
setIsOpen(!isOpen);
}
return (
<div
onClick={openClick}//⓵
className={`${isOpen ? 'open' : ''}`}//⓶
>
クリック
</div>
);
}
①はJSXでDOM要素にイベントリスナーをセットするのはおなじです。
②の部分でJSXの記法を使って3項演算子による条件式でクラス名をセットしています。つまり、状態変数isOpenがtrueならクラス名open
をつけてfalseなら空のクラスにするという処理です。
③は、その判断のための状態変数isOpen
をuseStateでセットしています。
④以下の処理は状態変数isOpen
を更新しています。ここで重要なのはDOM要素を操作しているのではないということです。
まとめ
”imperative”なアプローチと"declarative"なアプローチの違いを、実感していただけたでしょうか?
一番大きな違いは状態変数isOpen
があるかないかです。これが最初に説明した「基礎情報」です。今回はuseStateで管理する状態変数でしたが、Gutenbergのブロックの場合はブロックの属性(attributes)になることもあります。
従前のJavaScriptでは、「基礎情報」なしにDOMをレンダリングしてきたので、これに慣れ親しんだ方は"declarative"なアプローチには違和感を感じるのではないかと思います。私もその一人です。
しかし、"declarative"なアプローチは大きなメリットをもたらします。「基礎情報」の変化によって、再レンダリングが必要なDOM要素が複数あった場合を想像してください。
従来のJavaScriptでは再レンダリングしなければならないDOM要素を全て抽出し、適切なメソッドを選んで属性等を更新します。
ReactならJSXの定義で一元的に管理できるのが最大の利点です。それによって「基礎情報」を更新するだけで全てが再レンダリングされるのです。規模の大きなWebサイトでは保守性が格段の差が出てくると思います。
しかし、何でも"declarative"なアプローチがよいというわけではありません。。特定のユースケース、特にサードパーティのライブラリやフレームワークとの統合においては、直接のDOM操作が必要となる場合もあります。
そのため、Reactにおいても”imperative”なアプローチの方法は覚えておく必要はあると思います。