Once I happened to see a js state management article written by a foreigner in Nuggets. After reading through it, I decided to implement it myself in order to understand the internal mechanism of state management.

The state management library makes it very convenient to manage the state between components.

1. Subscription publishing module

This module is actually observer mode, a one-to-many dependency where all dependent objects are notified when an object’s state changes, triggering registered events.

In the Subject class, first define this. EventList to save the events that need to be registered, and then add subscribe, unsubscribe, publish, etc

Subscribe and unsubscribe :name represents the unique name of the registered event, and fn is the callback function for event name, indicating that all FN methods are registered to the collection named name

class Subject {
  constructor() {
    this.eventList = []
  }
  /** * Subscribe topic * @param {string} name Event name * @param {function} fn event method */
  subscribe(name, fn) {
    if (!this.eventList.hasOwnProperty(name)) {
      this.eventList[name] = []
    }
    this.eventList[name].push(fn)
    console.log('this.eventList: '.this.eventList);
  }
  /** * Unsubscribe topic * @param {string} name Event name * @param {function} fn event method */
  unsubscribe(name, fn) {
    var fns = this.eventList[name];
    if(! fns || fns.length ==0) { // If the event is not subscribed to, return directly
      return false
    }
    if(! fn) {// If you pass in a specific function, cancel all subscriptions corresponding to name
      fns.length = 0
    } else {
      for (var i = 0; i < fns.length; i++) {
        if (fn == fns[i]) {
          fns.splice(i, 1); }}}}/** * publish topic, trigger subscribe event */
  publish() {
    var name = Array.prototype.shift.call(arguments)	// Get the event name
    var fns = this.eventList[name]
    if(! fns || fns.length ==0) { // Did not subscribe to the event
      return false
    }
    for (var i = 0, fn; i < fns.length; i++) {
      fn = fns[i]
      fn.apply(this.arguments)}}}Copy the code

For the observer class, we pass in the subject, event name, and event method to register the event with the corresponding topic:

class Observer {
  constructor(subject, name, fn) {
    this.subject = subject
    this.name = name
    this.subject.subscribe(name, fn)
  }
}
Copy the code

2. The coreLibStoreclass

The core LibStore class needs to introduce the topic class of the subscription and publishing module above. State management is personally understood as a singleton topic. All state events are subscribed to and published under the same topic, so instantiate Subject once. The state data needs to be listened on and assigned. To create the LibStore class, you need to pass in the params parameter, and obtain actions, mutations, or {} by default.

constructor(params){
  var _self = this
  this._subject = new Subject()
  this.mutations = params.mutations ? params.mutations : {}
  this.actions = params.actions ? params.actions : {}
}
Copy the code

To determine the state of a LibStore object at any given time, you need to define status. There are three states:

this.status = 'resting';
this.status = 'mutation'; 
this.status = 'action';
Copy the code

Storing data state is also passed in from Params, but to listen for changes in data stored in LibStore, we introduced Proxy to listen for changes in state data every time it is accessed or changed, trigger topic publishing when state data is changed, and execute all methods that depend on stateChange events.

// Proxy status value to listen for state changes
this.state = new Proxy(params.state || {}, {
  get(state, key) {
    return state[key]
  },
  set(state, key, val) {
    if(_self.status ! = ='mutation') {
      console.warn('mutation is required to change the state value');
    }
    state[key] = val
    console.log('State change:${key}:${val}`)
    _self._subject.publish('stateChange', _self.state)
    _self.status = 'resting';
    return true}})Copy the code

Changing the data in state is done through commit or Dispatch methods

@param {string} name @param {string} newVal */
commit(name, newVal) {
  if (typeof (this.mutations[name]) ! ='function') {
    return fasle
  }
  console.group(`mutation: ${name}`);
  this.status = 'mutation'; // Change the state
  this.mutations[name](this.state, newVal);
  console.groupEnd();
  return true;
}
/** * Distribute the method that executes the action * @param key method property name * @param newVal state new value */
dispatch(key, newVal) {
  if (typeof (this.actions[key]) ! ='function') {
    return fasle
  }
  console.group(`action: ${key}`);
  this.actions[key](this, newVal);
  self.status = 'action';
  console.groupEnd();
  return true
}
Copy the code

Finally, the instantiated subject _subject is exposed for subsequent registration of stateChange events

getSubject() {
   return this._subject
 }
Copy the code

3. Instantiate coreLibStorecomponent

For those of you who use Vuex, this component must be familiar. It involves configuring state, mutations, and actions, and passing parameters into an instance of the core LibStore component class

import libStore from "./libStore";
let state = {
  count: 0
}
let mutations = {
  addCount(state, val) {
    state.count = val
  },
}
let actions = {
  updateCount(context, val) {
    context.commit('addCount', val); }}export default new libStore({
  state,
  mutations,
  actions
})
Copy the code

4. RegisterstateChangeThe event

The StoreChange class will be used as a descendant of the application component. The purpose is to register stateChange events with the component and get the update method of the descendant class, which will be triggered when state data changes.

Introduce the observer class in the object Store and subscription publishing module that just instantiated LibStore, and register the stateChange event and callback update method

import store from '@/assets/lib/store'
import { Observer } from './subject'
class StoreChange {
  constructor() {
    this.update = this.update || function () {};
    new Observer(store.getSubject(), 'stateChange'.this.update.bind(this))}}Copy the code

5. Application examples

The instance uses two components, Index and Detail, respectively representing two pages, and switches the mount through hash route to realize the jump. It should be noted that before mounting the component each time, the stateChange method registered in the singleton topic of the state object should be cleared to avoid repeated registration.

  • Index
<! -- Page art template -->
<div class="index">
  <h1>Home page</h1>
  <hr>
  <button id="btn1">Increase the quantity</button>
  <button id="btn2">To reduce the number of</button>
  <h3 id='time'><% = count% ></h3>
</div>
Copy the code
Js / / component
import StateChange from '@/assets/lib/stateChange'
import store from '@/assets/lib/store'
export default class Index extends StateChange{
  constructor($root){
    super(a)this.$root = $root
    this.render()
    document.querySelector('#btn1').addEventListener('click'.this.add.bind(this))
    document.querySelector('#btn2').addEventListener('click'.this.minus.bind(this))
  }
  render(){
    var indexTmpl = require('./index.art')
    this.$root.innerHTML =indexTmpl({count:store.state.count})
  }
  update(){
    document.querySelector('#time').textContent = store.state.count
  }
  add(){
    var count = store.state.count
    store.commit('addCount',++count)
  }
  minus(){
    var count = store.state.count
    store.dispatch('updateCount',--count)
  }
}
Copy the code
  • Detail
<! -- Page art template -->
<div class="detail">
  <h1>details</h1>
  <hr>
  <h3 id="count"><% = count% ></h3>
</div>
Copy the code
import StateChange from '@/assets/lib/stateChange'
import store from '@/assets/lib/store'
export default class Index extends StateChange {
  constructor($root){
    super(a)this.$root = $root
    this.render()
  }
  render(){
    var detailTmpl = require('./detail.art')
    this.$root.innerHTML = detailTmpl({count:store.state.count})
  }
}
Copy the code

The Demo preview

This article refers to native JavaScript to implement the state management system

Finally, thanks to the original author and share author! Complete code at Github, welcome to exchange and star!