【ITニュース解説】What Is “State” in JavaScript, and How to Use It in Your Projects
2025年09月24日に「Dev.to」が公開したITニュース「What Is “State” in JavaScript, and How to Use It in Your Projects」について初心者にもわかりやすく解説しています。
ITニュース概要
JavaScriptのStateとは、アプリの「現在の状態」を表すデータだ。ゲーム開発では、プレイヤー位置やスコア、盤面の状態などをStateで管理する。画面表示(DOM)に依存せず、Stateというデータで真の状態を管理することで、ゲームロジックが安定し、予測可能になる。パックマンゲームでのペレット管理を例に解説した。
ITニュース解説
JavaScriptにおける「State(状態)」という概念は、システムエンジニアを目指す上で非常に重要である。Stateとは、アプリケーションやゲームの現在の状況を表現する、根底にあるデータのことである。例えば、ゲームであればプレイヤーの現在位置、獲得したスコア、ボード上に何が存在するかといった、様々な要素を追跡する役割を担っている。ゲーム開発の文脈では、画面に視覚的に表示されている内容に左右されず、ゲームボードの「真の」状態を常に把握するために、このStateを個別のデータ構造(例えば配列)として保持することが非常に有効である。特に、複数の要素が同時にゲームボード上の同じマスを操作する可能性があるような状況では、画面に表示されるHTML要素(DOM)から独立してゲームのロジックを管理するための、信頼できる手段となる。
このStateという概念がプログラミングにおいて価値を持つのは、いくつかの重要な利点があるためである。まず、「関心の分離」が実現できる点が挙げられる。これは、ゲームの内部的なロジック(State)と、ユーザーに表示される画面の見た目(DOM)とを明確に区別して扱うことができるという考え方である。これにより、コードが整理され、理解しやすくなる。次に、「予測可能性」が高まる。Stateを適切に管理することで、ゲームの現在の状況が常に明確なデータとして存在するため、何が起こっているのか、次に何が起こるべきかを予測しやすくなる。これは、デバッグや機能追加の際に大きな助けとなる。最後に、「スケーラビリティ」が向上する。State管理のパターンは、単純なアプリケーションだけでなく、より複雑なゲームや大規模なアプリケーションにも適用可能であり、プロジェクトの成長に合わせて柔軟に対応できる基盤となる。
あるPac-Manゲームの開発プロジェクトにおいて、State管理の重要性は明確に示された。このプロジェクトでは、当初、ゲームボード上のペレットの表示にCSSクラスを利用していた。具体的には、20x20ピクセルのマスに白い背景色を与え、transform: scale(0.2)というCSSプロパティを使って、ペレットを小さく見せていた。Pac-Manがペレットのあるマスに入ると、そのマスのペレットを示すCSSクラスが削除され、スコアが更新されるという仕組みであった。この方法自体は、Pac-Manがペレットを食べるという視覚効果を問題なく実現していた。しかし、transform: scaleプロパティは、そのマスに含まれる他の要素も一緒に縮小してしまうという副作用があった。Pac-Manがペレットを食べる際には問題なかったものの、ゴーストがペレットのあるマスを通過する際に、ゴーストまで縮んでしまうという予期せぬ問題が発生したのである。
このゴーストが縮む問題を解決するため、まず、ゴーストがペレットのあるマスに入るときに一時的にペレットのCSSクラスを削除し、マスを離れるときに再度クラスを戻すという方法が試みられた。このアプローチは一時的には機能するように見えたが、実際のゲームプレイではゴーストがペレットのクラスを適切に戻さなかったり、本来ペレットがないはずの空のマスにペレットを再表示してしまったりと、動作が非常に不安定であった。この解決策が機能しなかったため、別の方法として、ゴーストが入るマスに関する全ての情報を変数に保持し、ゴーストがマスを離れるときにその情報を使ってペレットを正確に戻すというアイデアが考案された。しかし、このアイデアをGitHub Copilotに説明したところ、CopilotはState管理という、より洗練された別の解決策を提案してきたのである。
このプロジェクトの制作者は、当初、State管理はReactのような特定のフレームワークでのみ使用される概念だと考えていたが、Copilotの提案を通じて、ゲーム開発においてもプレイヤーの位置、スコア、ボード上の要素、そして今回のケースではペレットなど、様々な情報を追跡するためにStateが広く用いられることを学んだ。Copilotは、画面に表示されるHTML要素とそのCSSクラス(DOM)は、あくまで視覚的な情報を伝えるものであり、ゲームの内部ロジックを追跡するには必ずしも信頼できるとは限らないと指摘した。特に、Pac-Manやゴーストのように、複数の要素が同時に同じマスを変更する可能性がある場合、DOMに依存したロジックは容易に矛盾を生じさせる。そこで、各マスが現在何を含んでいるかを別の配列として保持する「State配列」を導入することで、視覚的に何が表示されているかに関わらず、ゲームボードの「真の」状態を常に把握できるようになったのである。
このState管理の実装では、ゲームボードを初期設定するsetGameBoard関数が修正された。この関数は、ゲームボードの設計図となるoverlayArrayという配列に基づいて、各マスに壁、ペレット、パワーペレット、空のスペース、Pac-Manの開始位置などの情報を設定し、対応する視覚的なCSSクラスを適用する役割を担っている。修正後のsetGameBoard関数には、この初期設定のループ処理の中で、新しく追加されたpelletState配列を更新するためのコードが組み込まれた。具体的には、各マスについてoverlayArrayの値をチェックし、もしそのマスがパワーペレット(値が2)を持つべきであればpelletState[i]に'powerPellet'を、通常のペレット(値が3)を持つべきであればpelletState[i]に'pellet'を設定する。どちらでもない場合はnullを設定することで、pelletState配列が、画面に何が表示されているかにかかわらず、すべてのマスの真の状態を常に保持するようにした。このように、export const pelletState = [];として定義された配列が、ゲームボードのペレットに関する「唯一の真実の源」として機能するようになったのである。
Pac-Manが移動し、ペレットを食べる際のStateの更新も行われた。Pac-Manを制御する関数内で、Pac-Manがペレットやパワーペレットを食べた後、単にそのマスの視覚的なCSSクラスを削除するだけでなく、pelletState配列内の対応する要素もnullに更新するコードが追加された。例えば、Pac-Manが現在いるマスがpelletクラスを含んでいる場合、まずペレットを食べる音を再生し、squares[pacmanCurrentIndex].classList.remove('pellet');で視覚的なペレットを削除し、続けてpelletState[pacmanCurrentIndex] = null;としてState配列を更新する。パワーペレットの場合も同様である。これにより、ゲームの内部的な状態が、ボード上で実際に起こっていることと常に一致するようになり、ゲームロジックの信頼性が大幅に向上した。
ゴーストがボード上を移動する際のStateの更新も重要である。ゴーストはペレットやパワーペレットのマスを通過するが、それらを食べることはない。更新されたコードでは、ゴーストが移動するたびにpelletState配列をチェックし、ゲームの視覚とロジックの整合性を保つようにしている。ゴーストがこれから入るマスが、pelletState配列によればペレットやパワーペレットを持つべきである場合、そのマスの視覚的なペレット(CSSクラス)は一時的に削除される。これは、ゴーストがペレットの上に表示されるようにするためである。しかし、ゴーストはペレットを食べないため、pelletState配列内の値は変更されずにそのまま保持される。その後、ゴーストが以前いたマスを離れる際に、再度pelletState配列がチェックされる。もしそのマスがペレットやパワーペレットを持つべき状態であれば、視覚的なペレット(CSSクラス)が復元され、ゴーストが移動した後もペレットが再び表示されるようになる。この仕組みにより、ゲームはどのマスに実際にペレットがあるかを常に正確に把握し、ゴーストの動きによって以前発生していたペレットの誤配置という視覚的な不具合も解消された。
結論として、JavaScriptにおけるStateとは、プログラムの現在の状況を示すデータである。Pac-Manゲームのようなプロジェクトでは、Stateはプレイヤーの位置、スコア、ボード上の要素などを追跡するために用いられる。Reactなどのフレームワークでは、Stateはフックやクラスプロパティといった特定の仕組みで管理されるが、プレーンなJavaScriptでは、変数、配列、またはオブジェクトを用いてStateを管理する。このプロジェクトでは、Stateを導入する以前は、ゲームロジックをDOMの視覚的な情報に依存させており、それが不安定さの原因となっていた。特に、Pac-Manやゴーストがペレットのあるマスに入った際に、JavaScriptのcontains()メソッドでペレットクラスの有無をチェックし、それに基づいてロジックを処理していた。しかし、State管理のアプローチに変更してからは、ゲームの安定性が飛躍的に向上した。この経験から、プロジェクト全体にわたってState管理を適用するようコードをリファクタリングする計画がある。State管理のアプローチは、プロジェクトやゲームに「唯一の真実の源」を提供する。DOMは単に画面に何が起こっているかを表示する役割を担うが、実際のデータはStateに存在するのである。視覚的なDOMに頼るのではなく、ゲームロジックのためにState管理を用いることで、ゲームの安定性とパフォーマンスを著しく向上させることが可能となる。