簡單了解一下pinia的結構

隨著 Vue3 的正式轉正,Pinia 也漸漸火了起來。所以要更新一下自己的知識樹了。這裡主要是看看新的狀態是什麼「形態」。

狀態的容器還是「reactive」

  • 按照官網教程,做一個簡單的例子:
import { defineStore } from 'pinia'

export const usePersonStore = defineStore('objectTest', {
  state: () => {
    return { 
      name: 'jyk',
      age: 18,
      info: {
        a1: '11',
        a2: '22'
      }
    }
  },
  // 也可以這樣定義狀態
  // state: () => ({ count: 0 })
  actions: {
    nameAction() {
      this.name += '11'
    }
  },
  getters: {
    ageTest(state) {
      // 會有程式碼自動補全!
      return state.age += 100
    }
  }
})
  • 組件里引用:
   import { usePersonStore } from './object.js'

   const xiaoming = usePersonStore()
  
   console.log('\n xiaoming:')
   console.dir(xiaoming)
   console.log('counter - keys :')
   console.log(Object.keys(xiaoming))
    
   console.log('counter - for in :')
   for (const key in xiaoming){
     console.log(key)
   }
{{xiaoming}}<br>
{{xiaoming.age}}<br>

  <div v-for="(item, key, index) in xiaoming">
    {{index}} -- {{key}}: {{item}}
  </div>

  • 然後看看效果

pinia的狀態結構.png

狀態果然採用了 reactive,只是內部結構有點茫然,為啥是這樣?

數據部分變成了 ref,錯,是 toRef

一開始看,是把數據部分變成了 ref,但是仔細一看,原理是toRef。好吧,大概是為了保證響應性,自動結構了。只是還是挺無語的。

有圖為證:

數據變成了toRef.png

getter 變成了 computed

這個在意料之中,只是為啥和數據在一個「層級」上?

getter變成了computed.png

action 和數據是一級的。

action 還是函數的形式,只是,應該掛在「原型」上面吧,為啥又和數據擠在一起了?

action還是函數.png

狀態是否需要遍歷?

為啥這麼在意 getter、action是不是和數據在一個「層級」上呢?因為我經常使用遍歷的方式。
試了一下,果然都出來了。

如果在使用狀態的時候,不需要用到遍歷的話,可以跳過。

template 模板

整體使用

{{xiaoming}}
  • 結果:
{ "$id": "objectTest", "name": "jyk", "age": 118, "info": { "a1": "11", "a2": "22" }, "ageTest": 118 }
  • 討論
    出現了$id和數據成員,雖然沒有出現 getter,但是被執行了一次。
    所以age變成了「118」。
    好吧,可能是我的用法不對。

分開使用

{{xiaoming.age}}<br><br>

不會觸發getter,age還是 18

{{xiaoming.age}}<br><br>
{{xiaoming.ageTest}}<br><br>

兩個都顯示為 「118」

v-for

  <div v-for="(item, key, index) in xiaoming">
    {{index}} -- {{key}}: {{item}}
  </div>
  • 結果
0 -- $id: objectTest
1 -- $onAction: function () { [native code] }
2 -- $patch: function $patch(partialStateOrMutator) { let subscriptionMutation; isListening = false; if (true) { debuggerEvents = []; } if (typeof partialStateOrMutator === "function") { partialStateOrMutator(pinia.state.value[$id]); subscriptionMutation = { type: MutationType.patchFunction, storeId: $id, events: debuggerEvents }; } else { innerPatch(pinia.state.value[$id], partialStateOrMutator); subscriptionMutation = { type: MutationType.patchObject, payload: partialStateOrMutator, storeId: $id, events: debuggerEvents }; } isListening = true; triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]); }
3 -- $reset: function $reset() { const newState = state ? state() : {}; this.$patch(($state) => { assign($state, newState); }); }
4 -- $subscribe: $subscribe(callback, options2 = {}) { if (typeof options2 === "boolean") { console.warn(`[\u{1F34D}]: store.$subscribe() no longer accepts a boolean as the 2nd parameter: Replace "store.$subscribe(fn, ${String(options2)})" with "store.$subscribe(fn, { detached: ${String(options2)} })". This will fail in production.`); options2 = { detached: options2 }; } const _removeSubscription = addSubscription(subscriptions, callback, options2.detached); const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state, oldState) => { if (isListening) { callback({ storeId: $id, type: MutationType.direct, events: debuggerEvents }, state); } }, assign({}, $subscribeOptions, options2))); const removeSubscription = () => { stopWatcher(); _removeSubscription(); }; return removeSubscription; }
5 -- $dispose: function $dispose() { scope.stop(); subscriptions = []; actionSubscriptions = []; pinia._s.delete($id); }
6 -- name: jyk
7 -- age: 118
8 -- info: { "a1": "11", "a2": "22" }
9 -- nameAction: function() { const _actionId = runningActionId; const trackedStore = new Proxy(store, { get(...args) { activeAction = _actionId; return Reflect.get(...args); }, set(...args) { activeAction = _actionId; return Reflect.set(...args); } }); return actions[actionName].apply(trackedStore, arguments); }
10 -- ageTest: 118
11 -- _hotUpdate: function(newStore) { originalHotUpdate.apply(this, arguments); patchActionForGrouping(store, Object.keys(newStore._hmrPayload.actions)); }

好吧,大概是我的使用方式不對。

for in

    console.log('counter - for in :')
    for (const key in xiaoming){
      console.log(key)
    }
  • 結果
counter - for in :
pinia.vue:42 $id
pinia.vue:42 $onAction
pinia.vue:42 $patch
pinia.vue:42 $reset
pinia.vue:42 $subscribe
pinia.vue:42 $dispose
pinia.vue:42 name
pinia.vue:42 age
pinia.vue:42 info
pinia.vue:42 nameAction
pinia.vue:42 ageTest
pinia.vue:42 _hotUpdate
  • 討論
    沒有出現 action。出現了內部設置的成員,以及數據、getter。總之和我想的不一樣。

Object.keys

    console.log('counter - keys :')
    console.log(Object.keys(xiaoming))
  • 結果
counter - keys :
pinia.vue:38 (12) ["$id", "$onAction", "$patch", "$reset", "$subscribe", "$dispose", "name", "age", "info", "nameAction", "ageTest", "_hotUpdate"]

我想的到底是啥樣的呢?

可以使用 class + reactive 實現一個充血實體類,比如這樣:

import { computed, reactive } from 'vue'

// 充血實體類
class TestClass {
  constructor (_info) {
    // 設置屬性,數組或者對象
    this.name = 'jyk'
    this.age = 18
    this.info = {
      a1: 'a1',
      a2: 'a2'
    }
  }
  // 通用賦值
  $set(model, clear = false) {
    if (clear) {
      Object.keys(this).forEach(key => {
        delete this[key]
      })
    }
    Object.assign(this, model)
  }
  actionTest() {
    this.age += 1
  }
  get getterTest() {
    const tmp = computed(() => { return this.age + 100})
    return tmp
  }
}

export default function() {
  const tmp = new TestClass()
  return reactive(tmp)
}

在組件里使用:

  const test2 = testClass()

  console.log(test2)
  console.log(test2.getterTest)
  console.log('\n 遍歷 Object.keys -----')
  console.log(Object.keys(test2))
  console.log('\n 遍歷 for in -----')
  for (const key in test2) {
    console.log(key, ':', test2[key])
  }
  console.log('\n 遍歷 for in 結束 -----')

獲取實例後,套上 reactive 就可以獲得響應性。

這樣數據部分在第一層,其他各種方法都在「原型」裡面,那麼在 v-for、 Object.keys 和for…in的時候,只會出現數據部分,沒有各種函數了。

整體結構也很簡潔。

  • 看看列印效果

class+reactive的方式.png

遍歷的情況也是挺好的。

Tags: