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

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

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

1
2
3
4
5
// src/pinia/index.js
import createPinia from './createPinia'
import defineStore from './defineStore'

export { createPinia, defineStore }

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

1
2
3
4
5
6
7
// src/pinia/createPinia.js
export default () => {
function install(app) {}
return {
install
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
// src/pinia/createPinia.js
import { reactive } from 'vue'

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

function install(app) {}

return {
install
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 暴露出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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。

1
2
3
4
5
6
7
8
// src/pinia/apis.js
export function patch({ value }) {
const store = this

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

给 store 添加上公共的 api。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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 对象。

1
2
3
4
5
6
7
8
9
10
11
// src/pinia/defineStore.js
export default (
name,
{
state, // function
getters,
actions
}
) => {
const store = {}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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

1
2
3
4
5
6
7
8
// 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。
1
2
3
4
5
6
7
8
9
// 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 的处理拆分到单个文件中。

1
2
3
4
5
6
7
8
9
10
11
// 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)
}
}
1
2
3
4
5
6
// src/pinia/options/action.js
export function createActions(store, actions) {
for (let method in actions) {
store[method] = actions[method]
}
}
1
2
3
4
5
6
7
8
9
10
11
// 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]
}
}
1
2
3
4
5
6
// src/pinia/options/index.js
import { createStates } from './state'
import { createActions } from './action'
import { createGetters } from './getter'

export { createStates, createActions, createGetters }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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

本小节完。