用 JS Proxy 打造一個狀態管理工具

Proxy 是什麼?

MDN: Proxy 物件被使用於定義基本操作的自定行為(例如:尋找屬性、賦值、列舉、函式調用等等)。

Proxy 是什麼?

Or as I call it...

有人想打聽,或對我們的物件動手腳的時候,我們把他的請求全部攔截下來,然後看我們要幹嘛 (設了一個陷阱)。

Proxy 是什麼?

看 Code 的話

const obj = {
  name: 'Kuan',
  age: 28,
}

const trappedObj = new Proxy(obj, {
  set: (target, key, value) => {
    console.log(target, key, value)
  },
  get: (target, key) => {
    console.log(target, key)
  }
})

// 觸發 set
trappedObj.name = 'Chun';

// 觸發 get
const b = trappedObj.name;

Proxy 是什麼?

有什麼好處?

  • 資料驗證/格式化

  • 給預設值

const kuan = new Proxy({is: ''}, {
  set: (target, key, value) => {
    if (key === 'is' && ['cool', 'handsome'].includes(value)) {
      target[key] = value
      
      return true
    }
    
    console.error('do you really think of that?!')
  }
})

kuan.is = 'ugly'

Proxy 拿來做 Obervable

剛剛說的聽起來好像 event listener?

(事情發生了我要做些什麼)

Proxy 拿來做 Obervable

簡易 obervable

function observe(target, callback) {
  return new Proxy(target, {
    set: (targetObject, prop, value) => {
      callback(prop, value)
      
      targetObject[prop] = value
    }
  })
}

const myState = observe({}, (key, value) => {
  doSomething(key, value)
})

myState.name = 'kuan';

Proxy 拿來做 State Management

參考 redux 的話就要做這些事情:

  • state 放在 store 裡面
  • store 裡面決定發生了什麼事件要把 state 變成怎樣
  • 誰都可以連到這個 store
  • 大家透過 dispatch 的方式跟 store 說我要變

Proxy 拿來做 State Management

state 放在 store 裡面:

class Store {
  constructor({ state }) {
    this.state = state || {};
  }
}

Proxy 拿來做 State Management

store 裡面決定發生了什麼事件要把 state 變成怎樣

之 要有可以註冊跟播送事件的東西

class EventController {
  constructor() {
    this.events = {}
  }
  
  subscribe(event, callback) {
    if (!this.events.hasOwnProperty(event)) {
      this.events[event] = [];
    }

    return this.events[event].push(callback);
  }

  publish(event, data = {}) {
    if (!this.events.hasOwnProperty(event)) {
      this.events[event] = [];
    }

    return this.events[event].map(callback => callback(data));
  }
}

Proxy 拿來做 State Management

store 裡面決定發生了什麼事件要把 state 變成怎樣

之 Store 也要有這個 event controller

class Store {
  constructor({ state }) {
    this.state = state || {};
    
    this.events = new EventController();
  }
}

Proxy 拿來做 State Management

store 裡面決定發生了什麼事件要把 state 變成怎樣

之 Store 要來決定 state 要變怎樣

(透過 mutation)

class Store {
  constructor({ state, mutations }) {
    this.state = state || {};
    
    this.events = new EventController();
    
    this.mutations = mutations || {};
  }
}

type Mutation = (
  state: StoreState, 
  payload: yourData
) => void

Proxy 拿來做 State Management

store 裡面決定發生了什麼事件要把 state 變成怎樣

之 Store 要來決定 state 要變怎樣

(state 是個 proxy)

class Store {
  constructor({ state, mutations }) {
    this.state = new Proxy(state || {}, {
      set: (currentState, key, value) => {
        currentState[key] = value;

        this.events.publish('stateChange', this.state);

        return true
      }
    });
    
    this.events = new EventController();
    
    this.mutations = mutations || {};
  }
}

Proxy 拿來做 State Management

來透過 dispatch 改變 state

class Store {
  constructor({ state, mutations }) {
    ...
  }
    
  dispatch(action, payload) {
    if(typeof this.mutations[action] !== 'function') {
      console.log(`Mutation "${mutation}" doesn't exist`);
      return false;
    }
    
    const newState = this.mutations[action](this.state, payload);
  
    this.state = Object.assign(this.state, newState);
  
    return;
  }
}

Proxy 拿來做 State Management

大家都可以連接 store

const state = {
  items: []
}

const mutations = {
  addItem(state, payload) {
    state.items.push(payload);

    return state;
  }
}

const store = new Store({
  state,
  mutations,
})

store.dispatch('addItem', 1)
store.dispatch('addItem', 2)
store.dispatch('addItem', 3)

Proxy 拿來做 State Management

大家都可以連接 store

class Component {
  constructor({ store }) {
    this.render = (count) => {
      document
        .querySelector('#root')
        .innerHTML = `現在有 ${count} 個東西`;
    }

    if(store instanceof Store) {
      store.events.subscribe('stateChange', ({items}) => {
        this.render(items.length)
      });
    }
  }
}

const a = new Component({store})

document.getElementById('btn').addEventListener('click', () => {
  store.dispatch('addItem', 1)
})

感ㄣ惜福

其他 reference

2.

Made with Slides.com