この記事は、ラクスパートナーズ AdventCalendar 2025の23日目の記事です。
(個人で25日連続投稿にチャレンジ中のカレンダーになります)
Webのフロントエンドについて勉強していると、
- 状態(またはstate)
- 状態管理
といった言葉をよく聞くと思います。
私も、
「『状態』って、Vue.jsのrefとかReactのuseStateを使って定義する変数だよね」
「あとVuexやReduxのような、ライブラリが管理する値も『状態』って言うよね」
というイメージはあったものの、曖昧な言葉でしか説明できていないことに気がつきました。
そこでちゃんと言語化できるようにしたいと思い、今回記事にしました。
結構人によって「状態」の定義が多少異なっていたりします。本記事は私の個人的な解釈ですので、参考程度にご覧ください。
誤っている箇所や補足などあれば、ぜひコメントいただけますと幸いです。
「状態」とは?
わかりやすく言うと、UIがどうなるかを決定するために必要なデータのことを「状態(state)」と言います。
より詳細に説明するには、まず宣言的UIと命令的UIについて理解する必要があります。
宣言的UIと命令的UIとは?
ReactやVue.jsのようなSPAを実装できるフレームワークは宣言的UIという仕組みになっています。宣言的UIとは、
「特定のデータが更新されたら、フレームワーク(ライブラリ)側でそのデータに対応するUIを自動で更新してくれる」
というものです。
しかし、ReactやVue.jsが登場する前のフロントエンド開発はそうなっておらず、命令的UIという仕組みで開発していました。
命令的UIとは、
データの変更とDOMの操作を開発者側が管理する
というものです。
例えば、
「カウントボタンを10回押したら、『10回カウントしました』というテキストを表示する」
という処理を実装する場合、バニラのHTMLとJavaScriptでは以下のようになります。
<div class="card">
<button id="countButton">count: 0</button>
<p id="message" style="display: none;">10回カウントしました</p>
</div>
let count = 0;
const button = document.getElementById("countButton");
const message = document.getElementById("message");
function render() {
button.textContent = `count: ${count}`;
if (count >= 10) {
message.style.display = "block";
} else {
message.style.display = "none";
}
}
button.addEventListener("click", () => {
count++;
render();
});
render();
命令的UIの場合は、上記のようにcount変数の更新とmessageのid名を持つpタグの表示判定を開発者側が実装しています。
同じようなコードをVue.js(宣言的UI)で実装すると以下になります。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<div class="card">
<button type="button" @click="count++">count: {{ count }}</button>
<p v-if="count >= 10">10回カウントしました</p>
</div>
</template>
いかがでしょうか? コードが非常に短くなりました。
Vue.jsでは、refで定義した変数の値を変更すると、Vue.js側が自動的にデータの変更を検出し、該当の変数を参照しているDOMを更新する仕組みになっています。
テンプレート内で ref を使用し、後から ref の値を変更した場合、Vue は自動的にその変更を検出し、それに応じて DOM を更新します。これは、依存関係追跡ベースのリアクティビティーシステムによって実現されています。コンポーネントが初めてレンダリングされるとき、Vue はレンダリング中に使用されたすべての ref を追跡します。その後 ref が変更されると、それを追跡しているコンポーネントの再レンダリングがトリガーされます。
上記のコードの流れをご説明すると、
- ボタンをクリックすると
count変数が+1される - Vue.js側が
count変数の変更を検知し、count: {{ count }}部分を更新したり、v-ifディレクティブでpタグの表示判定をしたりする - 上記を10回繰り返す
-
count >= 10を満たすと、pタグが表示される
ということが内部で起こっています。
(宣言的UIと命令的UIについては、こちらの記事でも説明しております)
改めて「状態」とは?
説明が長くなりましたが、先ほどの宣言的UIの例では、count変数の値によって、
- buttonタグのテキストの内容が変わる
- 条件を満たすとpタグが表示される
といったUIの変化が起きています。
このcount変数のように、
UIがどうなるかを決定するために必要なデータ
のことを「状態」と言います。
状態管理ライブラリで管理している「状態」とは?
先ほどはコンポーネント内で定義する「状態」について説明しました。
しかし、よく見かけるVuexやReduxなどの状態管理ライブラリの場合は、いったいどんな「状態」を管理するのでしょうか?
(ライブラリによって微妙に違うかもしれませんが)状態管理ライブラリが扱う「状態」とは、
アプリケーション全体で共有される、UIに影響を与えるデータ
です。
以下はVuexの概要です。
Vuex アプリケーションの中心にあるものはストアです。"ストア" は、基本的にアプリケーションの 状態(state) を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。
Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。
ストアの状態を直接変更することはできません。明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。
つまり、
- データをストア(Store)と呼ばれる場所で一元管理している
- ライブラリごとに特定の手順を踏むことで、ストアで管理しているデータをどこからでも参照したり更新できる(アプリ全体でデータを共有できる)
- データが変更されたら、状態管理ライブラリ側が変更を検知して、そのデータを参照しているUIを自動で更新する
というのがVuexの特徴となります。
(私が調べた限りではReduxもおおよその特徴、仕組みは同じでした)
このように、状態管理ライブラリでは、
アプリケーション全体で共有される、UIに影響を与えるデータ
を「状態」として管理しています。
状態管理ライブラリを使うべき状況
概ね、規模の大きいアプリケーションで状態管理ライブラリを使うことが推奨されています。
アプリケーションの規模が大きくなると、
- コンポーネントのネストが深くなり、propsのバケツリレーが発生してしまう
- 兄弟要素同士でのデータの受け渡しが面倒
- 管理する「状態」が多くなるため、複雑になる(エラーを追うのも大変)
といった問題が発生します。
こういった場合に状態管理ライブラリを導入すると、
- 「状態」を一元管理しているので、どのコンポーネントからでも同じデータを参照できる
- 特定の手順を踏魔ないと「状態」を更新できないので、堅牢かつ処理の流れを追いやすい
といった恩恵が受けられます。
逆に、小中規模のアプリケーションでは、状態管理ライブラリを導入する必要はなさそうです(コンポーネント内で定義した「状態」にデータを格納するだけで十分対応できるため)。
むしろ、Reduxの分のコードが増えることにより、かえって読みづらいコードになる恐れもあります。
以上となります。
「普段何気なく使っている言葉だけど、いざ説明してって言われたら言語化できないな…」
となることってありますよね。
今後はこういった用語解説もどんどんメモしていきたいと思います。
ここまで読んでいただきありがとうございました!
参考
以下、参考にさせていただいた記事です。
ありがとうございました!