React向け状態管理ライブラリを作ってみた

#11
2022.6.12

KyokaというReact向けの状態&ロジック管理ライブラリを作ってみました。このライブラリを使うと、こんな感じでロジックを書けます。

import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { ModelProvider, Observable, useModel, useObservable } from 'kyoka';

class Model {
  count = new Observable(0);

  addCount() {
    this.count.set(this.count.get() + 1);
  }
}

function Counter() {
  const model = useModel<Model>();
  const value = useObservable(model.count);

  return (
    <>
      {value}{' '}<button onClick={model.addCount.bind(model)}>Add count</button>
    </>
  );
}

const model = new Model();
const root = ReactDOM.createRoot(document.getElementById('app')!);

root.render(
  <ModelProvider model={model}>
    <Counter />
  </ModelProvider>
);

最低限のグローバルステート管理パターンをライブラリ化しただけです。Reactの状態管理ライブラリは色々ありますが、個人的にオブジェクト指向かつ最小限のライブラリが欲しかったのですが、見当たらなかったので作りました。この方法ならほぼボイラープレート要らずで、何よりホワイトボックスで直感的ではないでしょうか。

mutableなオブジェクトを扱うこともできるようになっています。

import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { ModelProvider, Observable, useModel, useObservable } from 'kyoka';

interface Todo {
  date: Date;
  text: string;
  key: number;
}

class Model {
  todos = new Observable<Todo[]>([]);
  key = 0;

  addTodo(text: string) {
    const todos = this.todos.get();
    const date = new Date();
    todos.push({ date, text, key: this.key });
    this.key++;
    this.todos.set(todos);
  }
}

function Todo() {
  const model = useModel<Model>();
  const todos = useObservable(model.todos);
  const [todo, setTodo] = React.useState<string>('');

  return (
    <>
      <input type="text" onChange={e => setTodo(e.target.value)}></input>
      <button onClick={model.addTodo.bind(model, todo)}>Add todo</button>
      {todos.map(t =>
        <div key={t.key}>{t.date.toLocaleTimeString()} {t.text}</div>
      )}
    </>
  );
};

const model = new Model();
const root = ReactDOM.createRoot(document.getElementById('app')!);

root.render(
  <ModelProvider model={model}>
    <Todo />
  </ModelProvider>
);

Observableに同じ参照をsetすることで、明示的なアップデートを行なっています。

現状はReact18のuseSyncExternalStoreに頼りきりですが、気が向いたらReact17以前や他のJSXライブラリでも動くようにするつもりです。