首頁 > web前端 > js教程 > 用 JavaScript 程式碼編寫狀態管理函式庫

用 JavaScript 程式碼編寫狀態管理函式庫

王林
發布: 2024-08-24 11:05:04
原創
902 人瀏覽過

Writing a state management library in lines of JavaScript

狀態管理是 Web 應用程式最重要的部分之一。從使用全域變數到React hooks,再到使用MobX、Redux 或XState 等第三方庫(僅舉這三個),它是引發最多討論的主題之一,因為掌握它以設計一個可靠且高效的應用程序。

今天,我建議基於可觀察量的概念,用不到 50 行 JavaScript 建立一個迷你狀態管理函式庫。這個當然可以按原樣用於小型項目,但除了這個教育練習之外,我仍然建議您為實際項目轉向更標準化的解決方案。

API定義

當開始一個新的函式庫專案時,重要的是從一開始就定義它的 API 是什麼樣子,以便在考慮技術實作細節之前凍結它的概念並指導它的開發。對於真正的項目,甚至可以在此時開始編寫測試來驗證庫的實現,因為它是根據 TDD 方法編寫的。

在這裡,我們想要導出一個類,我們將其稱為 State,該類將使用包含初始狀態的物件和單一觀察方法進行實例化,該觀察方法允許我們使用觀察者訂閱狀態變更。僅當這些觀察者的依賴項之一發生變更時才應執行。

要更改狀態,我們希望直接使用類別屬性,而不是透過像 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);
  }
}
登入後複製

這裡我們選擇使用內部中間狀態對象,它允許我們保留狀態值。我們還將觀察者儲存在內部觀察者數組中,當我們完成此實作時,該數組將很有用。

由於這兩個屬性僅在此類中使用,因此我們可以通過在它們前面添加 # 前綴並在類別上添加初始聲明來使用一些語法糖將它們聲明為私有:

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

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

  observe(observer) {
    this.#observers.push(observer);
  }
}
登入後複製

原則上這是一個很好的實踐,但我們將在下一步中使用代理,它們與私有屬性不相容。在不深入細節的情況下,為了使實作更容易,我們現在將使用公共屬性。

使用代理從狀態物件讀取數據

當我們概述該項目的規範時,我們希望直接在類別實例上存取狀態值,而不是作為其內部狀態物件的條目。

為此,我們將使用一個代理對象,該對象將在類別初始化時返回。

顧名思義,代理允許您為物件建立一個中介來攔截某些操作,包括其 getter 和 setter。在我們的例子中,我們建立一個代理,暴露第一個 getter,它允許我們暴露狀態物件的輸入,就好像它們直接屬於 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 時定義一個初始狀態對象,然後直接從該實例中檢索其值。現在讓我們看看如何操作它的數據。

新增 setter 來修改狀態值

我們新增了一個 getter,因此下一個邏輯步驟是新增一個 setter,允許我們操作狀態物件。

我們先檢查鍵是否屬於該對象,然後檢查值確實已更改以防止不必要的更新,最後用新值更新對象。

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
登入後複製

現在我們已經完成了資料讀寫部分。我們可以更改狀態值,然後檢索該更改。到目前為止,我們的實作還不是很有用,所以現在讓我們實作觀察者。

實施觀察者

我們已經有一個數組,其中包含在實例上聲明的觀察者函數,因此我們所要做的就是每當值發生變化時一一調用它們。

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!
登入後複製

太好了,我們現在正在對數據更改做出反應!

不過是個小問題。如果您到目前為止一直在關注,我們最初只想僅在觀察者的依賴項之一發生更改時才運行觀察者。但是,如果我們運行此程式碼,我們會看到每次狀態的一部分發生變更時每個觀察者都會運行。

那我們要如何辨識這些函數的依賴關係呢?

Identifying Function Dependencies with Proxies

Once again, Proxies come to our rescue. To identify the dependencies of our observer functions, we can create a proxy of our state object, run them with it as an argument, and note which properties they accessed.

Simple, but effective.

When calling observers, all we have to do is check if they have a dependency on the updated property and trigger them only if so.

Here is the final implementation of our mini-library with this last part added. You will notice that the observers array now contains objects allowing to keep the dependencies of each observer.

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!
登入後複製

And there you have it, in 45 lines of code we have implemented a mini state management library in JavaScript.

Going further

If we wanted to go further, we could add type suggestions with JSDoc or rewrite this one in TypeScript to get suggestions on properties of the state instance.

We could also add an unobserve method that would be exposed on an object returned by State.observe.

It might also be useful to abstract the setter behavior into a setState method that allows us to modify multiple properties at once. Currently, we have to modify each property of our state one by one, which may trigger multiple observers if some of them share dependencies.

In any case, I hope that you enjoyed this little exercise as much as I did and that it allowed you to delve a little deeper into the concept of Proxy in JavaScript.

以上是用 JavaScript 程式碼編寫狀態管理函式庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板