Pinia的使用与原理

Pinia 与 Vuex 对比

  • vuex:

    • ts兼容性不好
    • 命名空间的缺陷(只能有一个store)
    • mutation和action有区别
  • pinia:

    • 更简洁的API
    • ts兼容性更好
    • 无命名空间的缺陷(可以创建多个store)
    • 删除了mutation,统一在action中开发

使用方法

引入

// main.ts
import {
    createApp } from "vue";
import App from "./App.vue";
import {
    createPinia } from "@/my-pinia";

const app = createApp(App);
const pinia = createPinia()
console.log("createPinia", createPinia);
// 注册pinia
app.use(pinia);

app.mount("#app");

创建store

// stores/count.ts

// 方式1
import {
    reactive, computed, toRefs } from "vue";
import {
    defineStore } from "@/my-pinia";
// export const useCountStore = defineStore("counter", {
   
//   state: () => ({ count: 1 }),
//   getters: {
   
//     doubleCount: (store) => store.count * 2,
//   },
//   actions: {
   
//     // 同步异步都在actions中完成
//     addCount() {
   
//       this.count++;
//     },
//   },
// });

// 方式2

export const useCountStore = defineStore("count", () => {
   
  const state = reactive({
   
    count: 0,
  });
  const doubleCount = computed(() => state.count * 2);
  const addCount = () => state.count++;
  return {
   
    ...toRefs(state),
    doubleCount,
    addCount,
  };
}); 

页面调用

<script setup>
import {
      useCountStore } from "./stores/count";

const store = useCountStore();

</script>

<template>
  <div>
    {
  { store.count }}
    <div>数量:{
  { store.count }} getters:{
  { store.doubleCount }}</div>
    <button @click="store.addCount">增加</button>
  </div>
</template>

核心实现

  • 通过在app中注入一个对象pinia.state,触达每一个子组件,在定义时给state追加store。
  • 在获取时通过inject将store传入组件中。

createPinia.js : 创建插件并注入

  • 管理state及其下的store:
    • 基于effectScope生成scope,scope管理state,state内部存放所有的store,用于批量管理其中的响应式数据(控制响应式数据是否刷新视图)。
    • 每个store内部也有一个scope,管理内部的属性是否刷新视图。
  • 注入pinia:通过provide将pinia注入到app上,各组件实例可以获取到该pinia
import {
    markRaw, effectScope, ref } from "vue";
import {
    symbolPinia } from "./rootStore";
export function createPinia() {
   
  /**
   *  用法
   *      const pinia = createPinia();app.use(pinia);
   *  所以createPinia返回一个pinia插件
   *
   *  pinia需要有的能力
   *      不被响应式[markRaw]
   *      能被挂载到全局上
   */

  // 创建一个独立的scope,将所有的store都丢进去,后期可以统一管理store
  const scope = effectScope(true);

  const state = scope.run(() => ref({
   }));

  // 创建一个静态属性(markRaw),无法被响应式[表层]
  const pinia = markRaw({
   
    install(app) {
   
      // app.use(pinia)时,会调用pinia的install方法并传入app

      // 将pinia挂载到所有组件的属性上,组件可通过inject获取
      app.provide(symbolPinia, pinia);

      // vue2语法
      app.config.globalProperties.$pinia = pinia;
      pinia._a = app;
    },
    _a: null, //挂载app实例,后期可能用到
    _e: scope, //指向effectScope
    _s: new Map(),
    state,
  });
  return pinia;
}

defineStore.js : 创建store

  • 每一个store都是一个响应式对象reactive
import {
   
  effectScope,
  getCurrentInstance,
  inject,
  reactive,
  computed,
  toRefs,
} from "vue";
import {
    symbolPinia } from "@/my-pinia/rootStore.js";
export function defineStore(idOrOptions, setup) {
   
  // 整合参数 defineStore({id:'xx'}) defineStore('id',setup) defineStore('id',options)

  let id;
  let options;
  if (typeof idOrOptions == "string") {
   
    id = idOrOptions;
    options = setup;
  } else {
   
    id = idOrOptions.id;
    options = idOrOptions;
  }

  const isSetupStore = typeof setup === "function";

  function useStore() {
   
    // 保证useStore是在setup中执行的,只有在setup中才能通过组件实例注入父组件属性
    const currentInstant = getCurrentInstance();

    // 注入app中的pinia对象
    const pinia = currentInstant && inject(symbolPinia);

    // 判断pinia中是否有该store
    if (!pinia._s.has(id)) {
   
      // 第一次创建store
      if (isSetupStore) {
   
        createSetupStore(id, options, pinia);
      } else {
   
        createOptionStore(id, options, pinia);
      }
    }

    // 获取pinia._s中的store并返回
    const store = pinia._s.get(id);
    
    return store;
  
  }

  return useStore;
}

createSetupStore : 根据传入的函数返回store

// 函数式store(传入一个setup函数并返回对象)
function createSetupStore(id, setup, pinia) {
   

  // !!!这是最终挂载到state上的store,每个store都是一个响应式对象
  
  let store = reactive({
   });
  
  let scope;

  /**
   * 在根scope上再运行一次run,则此run也会被scope收集控制
   * setupScope => setup()
   */
  const setupScope = pinia._e.run(() => {
   
    // 创建一个scope,让store本身也有停止本身收集依赖的能力
    scope = effectScope();
    // 内部的setup也会被pinia控制
    return scope.run(() => setup());
  });

  // 包装action,将其this指向store
  function wrapAction(name, action) {
   
    return function () {
   
      // 此处可以有额外逻辑,且返回值也可以经过处理
      return action.apply(store, arguments);
    };
  }

  for (let key in setupScope) {
   
    let prop = setupScope[key];
    if (typeof prop === "function") {
   
      /**
       * 切片编程:此处主要目的是修改其this指向为当前store
       *          也可以加若干逻辑在其中
       */
      setupScope[key] = wrapAction(key, prop);
    }
  }

  // 覆盖store并挂载方法于store中。
  Object.assign(store, setupScope);
  // 挂载到pinia上
  pinia._s.set(id, store);

  return store;
}

createOptionStore(建议写法) :根据传入的配置返回store

  • 将选项配置整合一下再调用createSetupStore
// 普通的store(state、getters...):根据id和options,创建并挂载store至pinia中
function createOptionStore(id, options, pinia) {
   

  // 取出当前store的配置
  let {
    state, getters, actions } = options;

  let store = reactive({
   });

  // 提供一个setup函数
  function setup() {
   
    // 将state挂载到pinia上的state中
    pinia.state.value[id] = state ? state() : {
   };
    // pinia.state => markRaw({state})
    // state只被proxy,但是没有响应式,因此需要将其响应式
    // 将其state返回的值变为响应式的,便于computed收集依赖
    const localStore = toRefs(pinia.state.value[id]); 
    return Object.assign(
      localStore,
      actions,
      // 将getters用computed缓存,并暴露到store上
      
      Object.keys(getters).reduce((computedGetters, name) => {
   
        //   getters: {
   
        //     doubleCount: (store) => store.count * 2,
        //   },
        computedGetters[name] = computed(() => {
   
          // 如果此处pinia.state.value[id]不拿toRefs包裹
          // 则返回的是一个具体值,computed无法收集到store中的数据变化
          return getters[name].call(store, store);
        }); // 改变其this,并把store传入,两种写法
        return computedGetters;
      }, {
   })
    );
  }
  store = createSetupStore(id, setup, pinia);
  return store;
}

主文件(lib/index.js)

// index.js
export {
    createPinia } from "./createPinia";
export {
    defineStore } from "./defineStore";

// rootStore.js
export const symbolPinia = Symbol();

其余方法

  • $patch:批量更改store中的属性,本质上是合并对象(深拷贝)
  • $reset:重置store中的state为初始值(保存刚开始的值,调用时覆盖即可)
  • $subscribe:监听对应store中state所有的属性,当属性变化时触发回调watch(()=>store)
  • $onAction:监听store中的actions,当调用actions时触发回调(发布订阅,在wrapAction中发布)
  • $dispose:删除store(停止收集依赖,视图不根据数据更新了[effectScope.stop])

相关推荐

  1. Pinia使用原理

    2024-02-05 06:16:07       48 阅读
  2. pinia 使用方法

    2024-02-05 06:16:07       52 阅读
  3. Pinia定义及使用

    2024-02-05 06:16:07       26 阅读
  4. pinia vuex 区别

    2024-02-05 06:16:07       21 阅读
  5. Vue3 使用Pinia 存入取出数据

    2024-02-05 06:16:07       39 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-05 06:16:07       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-05 06:16:07       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-05 06:16:07       82 阅读
  4. Python语言-面向对象

    2024-02-05 06:16:07       91 阅读

热门阅读

  1. CentOS磁盘扩容

    2024-02-05 06:16:07       58 阅读
  2. shell脚本初始化mysql密码

    2024-02-05 06:16:07       51 阅读
  3. 开源软件,推动技术创新

    2024-02-05 06:16:07       55 阅读
  4. Redis实现:每个进程每30秒执行一次任务

    2024-02-05 06:16:07       53 阅读
  5. 2024 极术通讯-安谋科技2023精彩征程回顾

    2024-02-05 06:16:07       48 阅读
  6. 负载均衡SLB

    2024-02-05 06:16:07       40 阅读