JackAtlas

Command Palette

Search for a command to run...

Pinia 学习笔记(二)
over 2 years ago
前端开发

Pinia 学习笔记(二)

接下来,将 src/main.js 中对于 pinia 的引用改成对自己实现的引用。

// src/main.js
// import { createPinia } from 'pinia'
import { createPinia } from '@/pinia'

新建 src/pinia/index.js 作为库的入口文件。前面的 TodoList 中用到了两个方法:createPiniadefineStore

// src/pinia/index.js
import createPinia from './createPinia'
import defineStore from './defineStore'

export { createPinia, defineStore }

观察 app.use(createPinia()) 可知,createPinia 是一个方法,返回一个包含 install 方法的对象,可以作为 app.use 方法的参数。

// src/pinia/createPinia.js
export default () => {
  function install(app) {}
  return {
    install
  }
}

创建一个总的 Store,来存储所有通过 defineStore 方法创建的 store。

// src/pinia/createPinia.js
import { reactive } from 'vue'

export default () => {
  const piniaStore = reactive({})

  function install(app) {}

  return {
    install
  }
}

要把 piniaStore 暴露出来供 defineStore 调用,可以使用 vue 提供的 provide 方法。

// src/pinia/createPinia.js
import { reactive } from 'vue'

export default () => {
  const piniaStore = reactive({})

  function install(app) {
    app.provide('piniaStore', piniaStore)
  }

  return {
    install
  }
}

但是把整个 Store 暴露出来并不十分合理,可以改成把存入 store 的方法 setSubStore 暴露出来。

// src/pinia/createPinia.js
import { reactive } from 'vue'

export default () => {
  const piniaStore = reactive({})

  function setSubStore(name, store) {
    if (!piniaStore[name]) {
      piniaStore[name] = store
    }
    return piniaStore
  }

  function install(app) {
    app.provide('setSubStore', setSubStore)
  }

  return {
    install
  }
}

一些公共的 api 比如 $patch 可以在这里传入。新建一个文件 src/pinia/apis.js。

// src/pinia/apis.js
export function patch({ value }) {
  const store = this

  for (let key in value) {
    store[key] = value[key]
  }
}

给 store 添加上公共的 api。

// src/pinia/createPinia.js
import { reactive } from 'vue'
import { patch } from './apis'

export default () => {
  const piniaStore = reactive({})

  function setSubStore(name, store) {
    if (!piniaStore[name]) {
      piniaStore[name] = store
      piniaStore[name].$patch = patch
    }
    return piniaStore
  }

  function install(app) {
    app.provide('setSubStore', setSubStore)
  }

  return {
    install
  }
}

观察 src/store/todoList.js 中对 defineStore 方法的调用可知,该方法第一个参数是 store 的名称,第二个参数是 options 对象。

// src/pinia/defineStore.js
export default (
  name,
  {
    state, // function
    getters,
    actions
  }
) => {
  const store = {}
}

先来实现 state,并将 store 打印出来。

// src/pinia/defineStore.js
import { reactive, toRef } from 'vue'

export default (
  name,
  {
    state, // function
    getters,
    actions
  }
) => {
  const store = {}

  if (state && typeof state === 'function') {
    const _state = state()
    store.$state = reactive(_state)

    for (let key in _state) {
      store[key] = toRef(store.$state, key)
    }
  }

  console.log(store)
}

state

然后来实现 actions。

// src/pinia/defineStore.js
// ...
if (actions && Object.keys(actions).length > 0) {
  for (let method in actions) {
    store[method] = actions[method]
  }
}
// ...

actions

再来实现 gettersgetters 里面的方法都:

  1. 接收一个 state 参数;
  2. 其中的 this 指向 state
  3. 可以通过形如 this.anotherGetter() 这样的语句调用其他 getter
// src/pinia/defineStore.js
// ...
if (getters && Object.keys(getters).length > 0) {
  for (let getter in getters) {
    store[getter] = getters[getter].bind(store.$state, store.$state)
    store.$state[getter] = store[getter]
  }
}
// ...

优化一下代码,将 state、actions、getters 的处理拆分到单个文件中。

// src/pinia/options/state.js
import { reactive, toRef } from 'vue'

export function createStates(store, state) {
  const _state = state()
  store.$state = reactive(_state)

  for (let key in _state) {
    store[key] = toRef(store.$state, key)
  }
}

// src/pinia/options/action.js
export function createActions(store, actions) {
  for (let method in actions) {
    store[method] = actions[method]
  }
}

// src/pinia/options/getter.js
import { computed } from 'vue'

export function createGetters(store, getters) {
  for (let getter in getters) {
    store[getter] = computed(
      getters[getter].bind(store.$state, store.$state)
    )
    store.$state[getter] = store[getter]
  }
}

// src/pinia/options/index.js
import { createStates } from './state'
import { createActions } from './action'
import { createGetters } from './getter'

export { createStates, createActions, createGetters }

// src/pinia/defineStore.js
import { inject, reactive } from 'vue'

import { createStates, createActions, createGetters } from './options'

export default (
  name,
  {
    state, // function
    getters,
    actions
  }
) => {
  const store = {}

  state && typeof state === 'function' && createStates(store, state)
  actions &&
    Object.keys(actions).length > 0 &&
    createActions(store, actions)
  getters &&
    Object.keys(getters).length > 0 &&
    createGetters(store, getters)

  return () => {
    const setSubStore = inject('setSubStore')
    const piniaStore = setSubStore(name, reactive(store))

    return piniaStore[name]
  }
}

TodoList 的功能完美实现,将 store 打印出来看,有一点小瑕疵,但总体上前面分析的都实现了。

store