0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Dart】クロージャで学ぶ状態管理パターン(useState的設計)

Posted at

はじめに

Flutterの useState() や React Hooks の裏側では、
実は「クロージャによる状態保持」が動いています。

この記事では、Dartで自作のuseState的仕組みを作りながら、
関数による状態管理の本質を理解していきます。


ゴール

  • クロージャを使って「状態を持つ関数」を作る
  • ReactやFlutter Hooksの「useState」を理解する
  • GCと状態ライフサイクルを意識した設計を学ぶ

前提:クロージャは“状態を持つ関数”

クロージャとは「関数+定義時のスコープ」をセットで保持する仕組みでした。

Function makeCounter() {
  int count = 0;
  return () {
    count++;
    print(count);
  };
}

このように「関数なのに状態を持っている」のがクロージャの最大の特徴です。

これを応用すると「関数的状態管理(functional state management)」が実現できます。


useState風の設計をDartで再現

Reactの useState はこんな形ですね:

const [count, setCount] = useState(0);

これを Dartの関数だけで再現 してみましょう。


実装例:useStateを模倣する関数

typedef StateHook<T> = (T Function(), void Function(T));

StateHook<T> useState<T>(T initialValue) {
  T state = initialValue;

  T get() => state;
  void set(T newValue) {
    state = newValue;
  }

  return (get, set);
}

使い方

void main() {
  final (count, setCount) = useState(0);

  print(count()); // 0
  setCount(5);
  print(count()); // 5

  setCount(count() + 1);
  print(count()); // 6
}

解説

  • useState の内部では state という変数がクロージャによってキャプチャされる。
  • 関数 get()set() は、同じスコープの state を共有する。
  • つまり、外部からは見えない「隠れた状態」を保持している。

Flutter Hooksとの対応関係

React / Flutter Hooks Dart クロージャ版
useState() useState<T>()
get(状態取得) count()
set(状態更新) setCount()
状態保持 クロージャがスコープ内で state を保持
ライフサイクル管理 GC + スコープ破棄時に自動解放

状態更新をトリガーにする構造(useReducer風)

もう少し踏み込んで、「イベントによって状態を更新する」設計にしてみましょう。

typedef Reducer<S, A> = S Function(S, A);
typedef Dispatcher<A> = void Function(A);
typedef StateHookWithReducer<S, A> = (S Function(), Dispatcher<A>);

StateHookWithReducer<S, A> useReducer<S, A>(
  Reducer<S, A> reducer,
  S initialState,
) {
  S state = initialState;

  S get() => state;
  void dispatch(A action) {
    state = reducer(state, action);
  }

  return (get, dispatch);
}

使い方例:カウンターReducer

void main() {
  final (getCount, dispatch) = useReducer<int, String>(
    (state, action) {
      if (action == 'increment') return state + 1;
      if (action == 'reset') return 0;
      return state;
    },
    0,
  );

  print(getCount()); // 0
  dispatch('increment');
  print(getCount()); // 1
  dispatch('reset');
  print(getCount()); // 0
}

解説

  • dispatch がアクションを受け取り、reducer に渡す。
  • reducer は純関数的に状態を計算する。
  • 状態は依然としてクロージャに保持され、外部から直接アクセスできない。

これで ReactのuseReducer / Reduxの基本原理 に近い構造をDartだけで再現できました。


メモリ管理とGC

クロージャによる状態保持はヒープ上に変数を置くため、
リストやグローバル変数などに永続参照を残さなければ、
スコープを抜けた時点で自動解放されます。

void demo() {
  final (getValue, setValue) = useState(0);
  setValue(42);
  print(getValue()); // 42
} // ← demo()が終わるとクロージャもGC対象になる

ポイント

  • FlutterやReactでは、Widget / Componentのライフサイクルに合わせてクロージャが破棄される。
  • Dartではスコープを抜ければ自動で解放される(明示的なdispose不要)。

応用:複数状態を束ねる useStore 的パターン

class Store {
  final (getName, setName) = useState<String>('Alice');
  final (getAge, setAge) = useState<int>(20);

  void printState() {
    print('User: ${getName()} (${getAge()})');
  }
}

void main() {
  final store = Store();

  store.printState(); // User: Alice (20)
  store.setAge(25);
  store.setName('Bob');
  store.printState(); // User: Bob (25)
}

これにより、クロージャによる状態管理をクラス内で束ねることができます。
Flutterならこの仕組みを StateNotifierViewModel の内部に応用可能です。


まとめ

項目 内容
発想の原点 クロージャ=状態を持つ関数
useStateの仕組み 変数をキャプチャして保持
更新方法 setter / dispatch がクロージャ内の値を更新
メモリ管理 参照が切れればGCが自動解放
応用例 useReducer / useStore / Stateful関数設計

クロージャは「小さなコンテナ付き関数」として、 オブジェクト指向的なクラスを使わずとも、 シンプルな状態保持・更新ロジックを表現できます。

Flutter HooksやReact Hooksの「useState」も、 本質的にはこのクロージャ機構の上に成り立っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?