「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > JavaScript 行で状態管理ライブラリを作成する

JavaScript 行で状態管理ライブラリを作成する

2024 年 8 月 27 日に公開
ブラウズ:408

Writing a state management library in lines of JavaScript

状態管理は、Web アプリケーションの最も重要な部分の 1 つです。 React フックへのグローバル変数の使用から、これら 3 つだけを挙げると、MobX、Redux、XState などのサードパーティ ライブラリの使用に至るまで、これは、信頼性が高く効率的なアプリケーション。

今日は、オブザーバブルの概念に基づいて、50 行未満の JavaScript でミニ状態管理ライブラリを構築することを提案します。これは小規模なプロジェクトには確かにそのまま使用できますが、この教育的な演習を超えて、実際のプロジェクトではより標準化されたソリューションを使用することをお勧めします。

API定義

新しいライブラリ プロジェクトを開始するときは、技術的な実装の詳細を考える前に、コンセプトを固定して開発をガイドするために、最初から API がどのようなものかを定義することが重要です。実際のプロジェクトの場合、TDD アプローチに従って作成されたライブラリの実装を検証するために、この時点でテストの作成を開始することも可能です。

ここでは、初期状態を含むオブジェクトと、オブザーバーで状態の変更をサブスクライブできるようにする単一の観察メソッドでインスタンス化される、State と呼ぶ単一のクラスをエクスポートします。これらのオブザーバーは、依存関係の 1 つが変更された場合にのみ実行する必要があります。

状態を変更するには、setState.

のようなメソッドを使用するのではなく、クラス プロパティを直接使用したいと考えています。

コード スニペットは百聞は一見にしかずなので、最終的な実装は使用時にどのように見えるかを次に示します。

const state = new State({
  count: 0,
  text: '',
});

state.observe(({ count }) => {
  console.log('Count changed', count);
});

state.observe(({ text }) => {
  console.log('Text changed', text);
});

state.count  = 1;
state.text = 'Hello, world!';
state.count  = 1;

// Output:
// Count changed 1
// Text changed Hello, world!
// Count changed 2

State クラスの実装

コンストラクターで初期状態を受け入れ、後で実装する観察メソッドを公開する State クラスを作成することから始めましょう。

class State {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];
  }

  observe(observer) {
    this.observers.push(observer);
  }
}

ここでは、状態値を保持できる内部中間状態オブジェクトを使用することを選択します。また、この実装を完了するときに役立つ内部オブザーバー配列にオブザーバーを保存します。

これら 2 つのプロパティはこのクラス内でのみ使用されるため、# をプレフィックスとして付け、クラスに初期宣言を追加することで、少し構文を工夫してプライベートとして宣言できます。

class State {
  #state = {};
  #observers = [];

  constructor(initialState = {}) {
    this.#state = initialState;
    this.#observers = [];
  }

  observe(observer) {
    this.#observers.push(observer);
  }
}

原則として、これは良い習慣ですが、次のステップでプロキシを使用します。プロキシはプライベート プロパティと互換性がありません。詳細には立ち入らず、実装を容易にするために、今のところパブリック プロパティを使用します。

プロキシを使用して状態オブジェクトからデータを読み取る

このプロジェクトの仕様の概要を説明するとき、内部状態オブジェクトへのエントリとしてではなく、クラス インスタンスの状態値に直接アクセスしたいと考えました。

このために、クラスが初期化されるときに返されるプロキシ オブジェクトを使用します。

その名前が示すように、プロキシを使用すると、オブジェクトのゲッターやセッターを含む特定の操作をインターセプトするための仲介者を作成できます。私たちのケースでは、最初のゲッターを公開するプロキシを作成します。これにより、状態オブジェクトの入力を、あたかも State インスタンスに直接属しているかのように公開できるようになります。

class State {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];

    return new Proxy(this, {
      get: (target, prop) => {
        if (prop in target.state) {
          return target.state[prop];
        }

        return target[prop];
      },
    });
  }

  observe(observer) {
    this.observers.push(observer);
  }
}

const state = new State({
  count: 0,
  text: '',
});

console.log(state.count); // 0

State をインスタンス化するときに初期状態オブジェクトを定義し、そのインスタンスからその値を直接取得できるようになりました。次に、そのデータを操作する方法を見てみましょう。

状態値を変更するためのセッターの追加

ゲッターを追加したので、次の論理的なステップは、状態オブジェクトを操作できるようにするセッターを追加することです。

最初にキーがこのオブジェクトに属していることを確認し、次に不必要な更新を防ぐために値が実際に変更されたことを確認し、最後に新しい値でオブジェクトを更新します。

class State {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];

    return new Proxy(this, {
      get: (target, prop) => {
        if (prop in target.state) {
          return target.state[prop];
        }

        return target[prop];
      },
      set: (target, prop, value) => {
        if (prop in target.state) {
          if (target.state[prop] !== value) {
            target.state[prop] = value;
          }
        } else {
          target[prop] = value;
        }
      },
    });
  }

  observe(observer) {
    this.observers.push(observer);
  }
}

const state = new State({
  count: 0,
  text: '',
});

console.log(state.count); // 0
state.count  = 1;
console.log(state.count); // 1

これでデータの読み取りと書き込み部分は完了です。状態値を変更して、その変更を取得できます。これまでのところ、私たちの実装はあまり役に立たないので、ここでオブザーバーを実装しましょう。

オブザーバーの実装

インスタンス上で宣言されたオブザーバー関数を含む配列がすでにあるので、値が変更されるたびにオブザーバー関数を 1 つずつ呼び出すだけです。

class State {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];

    return new Proxy(this, {
      get: (target, prop) => {
        if (prop in target.state) {
          return target.state[prop];
        }

        return target[prop];
      },
      set: (target, prop, value) => {
        if (prop in target.state) {
          if (target.state[prop] !== value) {
            target.state[prop] = value;

            this.observers.forEach((observer) => {
              observer(this.state);
            });
          }
        } else {
          target[prop] = value;
        }
      },
    });
  }

  observe(observer) {
    this.observers.push(observer);
  }
}

const state = new State({
  count: 0,
  text: '',
});

state.observe(({ count }) => {
  console.log('Count changed', count);
});

state.observe(({ text }) => {
  console.log('Text changed', text);
});

state.count  = 1;
state.text = 'Hello, world!';

// Output:
// Count changed 1
// Text changed 
// Count changed 1
// Text changed Hello, world!

わかりました。現在、データの変更に対応しています!

小さな問題ですが。ここまで注意していただいた方のために、私たちは当初、オブザーバーの依存関係の 1 つが変更された場合にのみオブザーバーを実行したいと考えていました。ただし、このコードを実行すると、状態の一部が変更されるたびに各オブザーバーが実行されることがわかります。

しかし、では、これらの関数の依存関係をどのように特定すればよいのでしょうか?

プロキシを使用した関数の依存関係の特定

もう一度、プロキシが私たちを助けてくれます。オブザーバー関数の依存関係を特定するには、状態オブジェクトのプロキシを作成し、それを引数として実行し、どのプロパティにアクセスしたかを記録します。

シンプルですが効果的です。

オブザーバーを呼び出すときは、更新されたプロパティに対する依存関係があるかどうかを確認し、依存関係がある場合にのみオブザーバーをトリガーするだけです。

これは、最後の部分が追加されたミニライブラリの最終実装です。オブザーバー配列には、各オブザーバーの依存関係を保持できるオブジェクトが含まれていることがわかります。

class State {
  constructor(initialState = {}) {
    this.state = initialState;
    this.observers = [];

    return new Proxy(this, {
      get: (target, prop) => {
        if (prop in target.state) {
          return target.state[prop];
        }

        return target[prop];
      },
      set: (target, prop, value) => {
        if (prop in target.state) {
          if (target.state[prop] !== value) {
            target.state[prop] = value;

            this.observers.forEach(({ observer, dependencies }) => {
              if (dependencies.has(prop)) {
                observer(this.state);
              }
            });
          }
        } else {
          target[prop] = value;
        }
      },
    });
  }

  observe(observer) {
    const dependencies = new Set();

    const proxy = new Proxy(this.state, {
      get: (target, prop) => {
        dependencies.add(prop);
        return target[prop];
      },
    });

    observer(proxy);
    this.observers.push({ observer, dependencies });
  }
}

const state = new State({
  count: 0,
  text: '',
});

state.observe(({ count }) => {
  console.log('Count changed', count);
});

state.observe(({ text }) => {
  console.log('Text changed', text);
});

state.observe((state) => {
  console.log('Count or text changed', state.count, state.text);
});

state.count  = 1;
state.text = 'Hello, world!';
state.count  = 1;

// Output:
// Count changed 0
// Text changed 
// Count or text changed 0 
// Count changed 1
// Count or text changed 1 
// Text changed Hello, world!
// Count or text changed 1 Hello, world!
// Count changed 2
// Count or text changed 2 Hello, world!

これで、45 行のコードで JavaScript のミニ状態管理ライブラリを実装できました。

さらに進む

さらに進めたい場合は、JSDoc で型の候補を追加するか、これを TypeScript で書き換えて、状態インスタンスのプロパティに関する候補を取得できます。

State.observe.

によって返されるオブジェクトで公開される unobserve メソッドを追加することもできます。

セッターの動作を setState メソッドに抽象化し、複数のプロパティを一度に変更できるようにすることも役立つ場合があります。現在、状態の各プロパティを 1 つずつ変更する必要があるため、一部のオブザーバーが依存関係を共有している場合、複数のオブザーバーがトリガーされる可能性があります。

いずれにせよ、この小さな演習を私と同じように楽しんでいただき、JavaScript のプロキシの概念をもう少し深く掘り下げることができたことを願っています。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/jverneaut/writing-a-state-management-library-in-50-lines-of-javascript-3l41?1 侵害がある場合は、study_golang@163 までご連絡ください。 .comを削除してください
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3