粗略阅读pinia源码,对比vuex实现

从createPinia开始

ts 复制代码
let activePinia: Pinia | undefined
const setActivePinia: _SetActivePinia = (pinia) => (activePinia = pinia)

function createPinia(): Pinia {
  const scope = effectScope(true)
  const state = scope.run<Ref<Record<string, StateTree>>>(() =>
    ref<Record<string, StateTree>>({})
  )!

  let _p: Pinia['_p'] = []

  const pinia: Pinia = markRaw({
    install(app: App) {
      setActivePinia(pinia)
      if (!isVue2) {
        pinia._a = app
        app.provide(piniaSymbol, pinia)
        app.config.globalProperties.$pinia = pinia
      }
    },
    _p, // 记录插件
    _a: null, // 连接到当前pinia的App
    _e: scope,
    _s: new Map<string, StoreGeneric>(), // 记录该pinia注册的各个store
    state, // root state
  })

  return pinia
}
  • 截取了部分代码,以上就是pinia实例有哪些属性

有了pinia,可以定义store了

ts 复制代码
function defineStore(
  idOrOptions: any,
  setup?: any,
  setupOptions?: any
): StoreDefinition {
  let id: string
  let options:
    | DefineStoreOptions<
        string,
        StateTree,
        _GettersTree<StateTree>,
        _ActionsTree
      >
    | DefineSetupStoreOptions<
        string,
        StateTree,
        _GettersTree<StateTree>,
        _ActionsTree
      >

  const isSetupStore = typeof setup === 'function'
  if (typeof idOrOptions === 'string') {
    id = idOrOptions
    options = isSetupStore ? setupOptions : setup
  } else {
    options = idOrOptions
    id = idOrOptions.id
  }

  function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
    pinia = inject(piniaSymbol, null)
    if (pinia) setActivePinia(pinia)

    pinia = activePinia!

    if (!pinia._s.has(id)) { // 判断当前store是否注册在当前pinia中,没有则注册
      // 有两种创建方式,一种是setup和不使用setup
      if (isSetupStore) {
        createSetupStore(id, setup, options, pinia)
      } else {
        createOptionsStore(id, options as any, pinia)
      }
    }

    const store: StoreGeneric = pinia._s.get(id)! // 注册过就可以拿到实例了
    return store as any
  }

  useStore.$id = id

  return useStore
}
  • 总结这部分代码,对于defineStore只是将当前的pinia获取到后,对于传入的参数进行解析(有两种创建方式,第一种可以使用setup函数,第二种则是对象),判断当前pinia是否注册过当前store,没有则进入注册,后续就可以拿到同一个store实例并返回

创建store挂载到pinia身上

ts 复制代码
function createSetupStore<
  Id extends string,
  SS extends Record<any, unknown>,
  S extends StateTree,
  G extends Record<string, _Method>,
  A extends _ActionsTree,
>(
  $id: Id,
  setup: () => SS,
  options:
    | DefineSetupStoreOptions<Id, S, G, A>
    | DefineStoreOptions<Id, S, G, A> = {},
  pinia: Pinia,
  hot?: boolean,
  isOptionsStore?: boolean
): Store<Id, S, G, A> {
  let scope!: EffectScope

  let isListening: boolean // set to true at the end
  let isSyncListening: boolean // set to true at the end
  const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined  // 第一次创建拿不到即为undefined

  if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
    // 第一次拿对于pinia对应的state进行初始化
    if (isVue2) {
      set(pinia.state.value, $id, {})
    } else {
      pinia.state.value[$id] = {}
    }
  }

  let activeListener: Symbol | undefined
  function $patch(
    partialStateOrMutator:
      | _DeepPartial<UnwrapRef<S>>
      | ((state: UnwrapRef<S>) => void)
  ): void {

    if (typeof partialStateOrMutator === 'function') {
      partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>) // 传递了函数则执行函数并将对应的state作为参数传入
    } else { // 如果是对象形式,则根据参数去state中寻找到对应的值进行更新
      mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
    }

  }
  // 调用方法,并触发订阅
  function wrapAction(name: string, action: _Method) {
    return function (this: any) {
      setActivePinia(pinia)
      const args = Array.from(arguments)

      const afterCallbackList: Array<(resolvedReturn: any) => any> = []
      const onErrorCallbackList: Array<(error: unknown) => unknown> = []
      function after(callback: _ArrayType<typeof afterCallbackList>) {
        afterCallbackList.push(callback)
      }
      function onError(callback: _ArrayType<typeof onErrorCallbackList>) {
        onErrorCallbackList.push(callback)
      }

      // @ts-expect-error
      triggerSubscriptions(actionSubscriptions, {
        args,
        name,
        store,
        after,
        onError,
      })

      let ret: unknown
      try {
        ret = action.apply(this && this.$id === $id ? this : store, args)
        // handle sync errors
      } catch (error) {
        triggerSubscriptions(onErrorCallbackList, error)
        throw error
      }

      if (ret instanceof Promise) {
        return ret
          .then((value) => {
            triggerSubscriptions(afterCallbackList, value)
            return value
          })
          .catch((error) => {
            triggerSubscriptions(onErrorCallbackList, error)
            return Promise.reject(error)
          })
      }

      // trigger after callbacks
      triggerSubscriptions(afterCallbackList, ret)
      return ret
    }
  }

  const partialStore = {
    _p: pinia,
    $id,
    $onAction: addSubscription.bind(null, actionSubscriptions),
    $patch,
  } as _StoreWithState<Id, S, G, A>

  if (isVue2) {
    partialStore._r = false
  }

  const store: Store<Id, S, G, A> = reactive(partialStore) as unknown as Store<Id, S, G, A> // 包裹成响应式

  pinia._s.set($id, store as Store) // 挂载到pinia对应属性中

  const runWithContext =
    (pinia._a && pinia._a.runWithContext) || fallbackRunWithContext
  
  // 执行setup函数,拿到返回值
  const setupStore = runWithContext(() =>
    pinia._e.run(() => (scope = effectScope()).run(setup)!)
  )!

  // overwrite existing actions to support $onAction
  for (const key in setupStore) {
    const prop = setupStore[key]

    if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
      if (!isOptionsStore) {
        if (initialState && shouldHydrate(prop)) {
          if (isRef(prop)) {
            prop.value = initialState[key]
          } else {
            mergeReactiveObjects(prop, initialState[key])
          }
        }
        // transfer the ref to the pinia state to keep everything in sync
        /* istanbul ignore if */
        if (isVue2) {
          set(pinia.state.value[$id], key, prop)
        } else {
          pinia.state.value[$id][key] = prop
        }
      }
    } else if (typeof prop === 'function') {
      const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop)
      if (isVue2) {
        set(setupStore, key, actionValue)
      } else {
        setupStore[key] = actionValue
      }

    }
  }
  
  // 根据传入参数更新store
  if (isVue2) {
    Object.keys(setupStore).forEach((key) => {
      set(store, key, setupStore[key])
    })
  } else {
    assign(store, setupStore)
    // allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
    // Make `storeToRefs()` work with `reactive()` #799
    assign(toRaw(store), setupStore)
  }

  // use this instead of a computed with setter to be able to create it anywhere
  // without linking the computed lifespan to wherever the store is first
  // created.
  Object.defineProperty(store, '$state', {
    get: () => pinia.state.value[$id],
    set: (state) => {
      $patch(($state) => {
        assign($state, state)
      })
    },
  })

  return store
}
  • 对于createOptionsStore方法则是写一个setup方法对options进行处理,然后调用createSetupStore方法
  • 总结这部分代码,就是对于传入的参数state和action分别进行处理,然后挂载到当前活跃的pinia实例属性上,针对id作为唯一键名区别不同的store,同时也避免多次创建(单例模式)

和vuex对比

-vuex源码,这篇文章有写vuex的大致实现

  • pinia提供了composition-api的写法
  • 没有modules嵌套结构和命名空间,取而代之的是多个store实例
  • 不需要动态添加store,对于pinia直接使用useXxxStore,第一次使用的时候会挂载到当前createPinia 产生的实例属性上
  • 没有冗余的mutations
相关推荐
学习使我快乐0120 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199521 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery