Rumah  >  Artikel  >  hujung hadapan web  >  Menulis perpustakaan pengurusan negeri dalam baris JavaScript

Menulis perpustakaan pengurusan negeri dalam baris JavaScript

王林
王林asal
2024-08-24 11:05:04851semak imbas

Writing a state management library in lines of JavaScript

Pengurusan negeri ialah salah satu bahagian terpenting dalam aplikasi web. Daripada penggunaan pembolehubah global kepada cangkuk React kepada penggunaan perpustakaan pihak ketiga seperti MobX, Redux atau XState untuk menamakan hanya 3 ini, ia merupakan salah satu topik yang paling banyak menjana perbincangan kerana ia adalah penting untuk menguasainya untuk mereka bentuk aplikasi yang boleh dipercayai dan cekap.

Hari ini, saya mencadangkan untuk membina perpustakaan pengurusan negeri mini dalam kurang daripada 50 baris JavaScript berdasarkan konsep boleh diperhatikan. Yang ini sudah tentu boleh digunakan sebagaimana adanya untuk projek kecil, tetapi selain daripada latihan pendidikan ini, saya masih mengesyorkan anda untuk beralih kepada penyelesaian yang lebih standard untuk projek sebenar anda.

Definisi API

Apabila memulakan projek perpustakaan baharu, adalah penting untuk menentukan rupa APInya dari awal untuk membekukan konsepnya dan membimbing pembangunannya sebelum memikirkan butiran pelaksanaan teknikal. Untuk projek sebenar, anda juga boleh mula menulis ujian pada masa ini untuk mengesahkan pelaksanaan perpustakaan kerana ia ditulis mengikut pendekatan TDD.

Di sini kami ingin mengeksport satu kelas yang akan kami panggil State yang akan dijadikan instantiated dengan objek yang mengandungi keadaan awal dan kaedah pemerhatian tunggal yang membolehkan kami melanggan perubahan keadaan dengan pemerhati. Pemerhati ini hanya boleh dilaksanakan jika salah satu tanggungan mereka telah berubah.

Untuk menukar keadaan, kami mahu menggunakan sifat kelas secara langsung dan bukannya melalui kaedah seperti setState.

Oleh kerana coretan kod bernilai seribu perkataan, berikut adalah rupa pelaksanaan akhir kami dalam penggunaan:

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

Melaksanakan kelas Negeri

Mari kita mulakan dengan mencipta kelas Negeri yang menerima keadaan awal dalam pembinanya dan mendedahkan kaedah pemerhatian yang akan kita laksanakan kemudian.

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

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

Di sini kami memilih untuk menggunakan objek keadaan perantaraan dalaman yang membolehkan kami mengekalkan nilai keadaan. Kami juga menyimpan pemerhati dalam tatasusunan pemerhati dalaman yang akan berguna apabila kami melengkapkan pelaksanaan ini.

Memandangkan 2 sifat ini hanya akan digunakan di dalam kelas ini, kami boleh mengisytiharkannya sebagai peribadi dengan sedikit gula sintaktik dengan meletakkan awalannya dengan # dan menambah pengisytiharan awal pada kelas:

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

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

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

Pada dasarnya ini adalah amalan yang baik, tetapi kami akan menggunakan Proksi dalam langkah seterusnya dan ia tidak serasi dengan harta persendirian. Tanpa perincian dan untuk memudahkan pelaksanaan ini, kami akan menggunakan hartanah awam buat masa ini.

Baca data daripada objek keadaan dengan Proksi

Apabila kami menggariskan spesifikasi untuk projek ini, kami mahu mengakses nilai keadaan secara langsung pada contoh kelas dan bukan sebagai entri kepada objek keadaan dalamannya.

Untuk ini, kami akan menggunakan objek proksi yang akan dikembalikan apabila kelas dimulakan.

Seperti namanya, Proksi membenarkan anda membuat perantara untuk objek memintas operasi tertentu, termasuk pemula dan penetapnya. Dalam kes kami, kami mencipta Proksi yang mendedahkan pengambil pertama yang membolehkan kami mendedahkan input objek keadaan seolah-olah ia dimiliki secara langsung kepada tika Negeri.

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

Sekarang kita boleh mentakrifkan objek keadaan awal apabila menginstant State dan kemudian mendapatkan nilainya terus daripada kejadian itu. Sekarang mari lihat cara memanipulasi datanya.

Menambah penetap untuk mengubah suai nilai keadaan

Kami menambah pengambil, jadi langkah logik seterusnya ialah menambah penetap yang membolehkan kami memanipulasi objek keadaan.

Kami mula-mula menyemak sama ada kunci kepunyaan objek ini, kemudian menyemak sama ada nilai itu memang telah berubah untuk mengelakkan kemas kini yang tidak perlu, dan akhirnya mengemas kini objek dengan nilai baharu.

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

Kami kini selesai dengan bahagian membaca dan menulis data. Kita boleh menukar nilai keadaan dan kemudian mendapatkan semula perubahan itu. Setakat ini pelaksanaan kita tidak begitu berguna, jadi mari kita laksanakan pemerhati sekarang.

Pemerhati Pelaksana

Kami sudah mempunyai tatasusunan yang mengandungi fungsi pemerhati yang diisytiharkan pada contoh kami, jadi apa yang perlu kami lakukan ialah memanggilnya satu demi satu apabila nilai telah berubah.

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!

Bagus, kami kini bertindak balas terhadap perubahan data!

Masalah kecil. Jika anda telah memberi perhatian sejauh ini, kami pada asalnya mahu menjalankan pemerhati hanya jika salah satu kebergantungan mereka berubah. Walau bagaimanapun, jika kita menjalankan kod ini, kita melihat bahawa setiap pemerhati berjalan setiap kali bahagian negeri diubah.

Tetapi bagaimanakah kita boleh mengenal pasti kebergantungan fungsi ini?

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.

Atas ialah kandungan terperinci Menulis perpustakaan pengurusan negeri dalam baris JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel sebelumnya:Mesin JavaScript selariArtikel seterusnya:Mesin JavaScript selari