• 在Vue3项目中使用pinia代替Vuex进行数据存储


    pinia是一个vue的状态存储库,你可以使用它来存储、共享一些跨组件或者页面的数据,使用起来和vuex非常类似。pina相对Vuex来说,更好的ts支持和代码自动补全功能。本篇随笔介绍pinia的基础用法以及持久化存储的一些用法,供参考学习。

    pinia在2019年11月开始时候是一个实验项目,目的就是重新设计一个与组合API匹配的vue状态存储。基本原则和原来还是一样的,pinia同时支持vue2和vue3,且不要求你必须使用Vue3的组合API。不管是使用vue2或者vue3,pinia的API是相同的,文档是基于vue3写的。

    Pinia 是 Vuex4 的升级版,也就是 Vuex5; Pinia 极大的简化了Vuex的使用,是 Vue3的新的状态管理工具;Pinia 对 ts的支持更好,性能更优, 体积更小,无 mutations,可用于 Vue2 和 Vue3;Pinia支持Vue Devtools、 模块热更新和服务端渲染。

    1、pinia的安装和使用

    安装pinia(https://pinia.vuejs.org/)

    npm install pinia

    在main.j或者main.ts中引入使用

    import { createPinia } from 'pinia'
    
    app.use(createPinia())

    下面就是使用pinia的一个例子。这样你就创建了一个状态存储。

    // stores/counter.js
    import { defineStore } from 'pinia'
    
    export const useCounterStore = defineStore('counter', {
      state: () => {
        return { count: 0 }
      },
      // 也可以这样定义状态
      // state: () => ({ count: 0 })
      actions: {
        increment() {
          this.count++
        },
      },
    })

    在组件中使用:

    import { useCounterStore } from '@/stores/counter'
    
    export default {
      setup() {
        const counter = useCounterStore()
    
        counter.count++
        // 编辑器会有代码提示 
        counter.$patch({ count: counter.count + 1 })
        // 也可以使用action来代替
        counter.increment()
      },
    }
    如果你不是很喜欢setup函数和组合API,pinia也有类似vuex的map的功能。你可以用上面的方式定义你的store,但是使用时用mapStores(), mapState(),或者 mapActions():
    const useCounterStore = defineStore('counter', {
      state: () => ({ count: 0 }),
      getters: {
        double: (state) => state.count * 2,
      },
      actions: {
        increment() {
          this.count++
        }
      }
    })
    
    const useUserStore = defineStore('user', {
      // ...
    })
    
    export default {
      computed: {
        // 其他计算属性
        // ...
        // 可以使用 this.counterStore 和 this.userStore获取
        ...mapStores(useCounterStore, useUserStore)
        // 可以使用 this.count 和this.double获取
        ...mapState(useCounterStore, ['count', 'double']),
      },
      methods: {
        // 可以使用 this.increment()调用
        ...mapActions(useCounterStore, ['increment']),
      },
    }

    与vue4之前的版本相比,pinia的API是有很多不同的,即:

    • 去掉了mutation。因为好多人认为mutation是多余的。以前它方便devtools集成,现在这不是个问题了。
    • 不用在写复杂的ts类型包装,所有的都是有类型的,API设计的都是尽量符合ts的类型推断
    • 不再使用一个莫名其妙的字符串了,只需要导入一个函数,调用他们就行了,同时还有代码自动补全
    • 不需要动态添加store了,因为它们现在本来就是动态。如果你想,你随时可以手动去写一个store。
    • 没有复杂的嵌套模块了。你仍然可以在一个store中导入其他的store来实现嵌套模块,但是pinia还是推荐使用一个扁平的结构。但是即使你使用循环依赖也没关系。
    • 不再需要命名空间了。因为现在store本来就是扁平结构了。你也可以理解为所有的store本来就有命名空间了。
    你的应用中的全局数据需要保存在store中。在很多地方你都要使用这些数据,比如说,用户信息需要在导航栏中显示,也需要在个人中心显示。还有些数据,需要暂存起来,比如一个需要分好几页填写的表单。
    在pinia中,store是通过defineStore()方法定义的,它的第一个参数就是一个唯一的名字:
    import { defineStore } from 'pinia'
    
    export const useStore = defineStore('main', {
      // other options...
    })

    上面只是定义了store,在setup函数中调用了useStore()时,才会创建store:

    import { useStore } from '@/stores/counter'
    
    export default {
      setup() {
        const store = useStore()
    
        return {
          // 你可以返回store这个对象,然后就可以在template中使用了
          store,
        }
      },
    }

    在store实例化以后,你就可以调用到store中定义的state、getters和actions了。为了让解构的值还保持响应式,你需要用到storeToRefs()方法。它会给响应式的数据创建ref。

    import { storeToRefs } from 'pinia'
    
    export default defineComponent({
      setup() {
        const store = useStore()
        // `name` 和 `doubleCount` 是响应式的
        // 插件增加的属性也会创建ref
        // 但是会自动跳过action或者不是响应性的属性
        const { name, doubleCount } = storeToRefs(store)
    
        return {
          name,
          doubleCount
        }
      },
    })

    默认情况下,你可以在store实例上直接获取或者修改state:

    const store = useStore()
    store.counter++

    也可以调用$reset()方法来把state恢复为初始值:

    const store = useStore()
    store.$reset()

    除了直接修改store里的值store.counter++,你也可以是用$patch方法。你可以同时修改多个值:

    store.$patch({
      counter: store.counter + 1,
      name: 'Abalam',
    })

    或者$patch接收一个函数作为参数,来简化改变数组的写法:

    store.$patch((state) => {
      state.items.push({ name: 'shoes', quantity: 1 })
      state.hasChanged = true
    })

    2、pinia的持久化存储处理

    你可以用$subscribe()来侦听state的改变,持久化一般存储在localStorage和sessionStorage。

    localStorage和sessionStorage差别

    localStorage和sessionStorage一样都是用来存储客户端临时信息的对象。

    他们均只能存储字符串类型的对象(虽然规范中可以存储其他原生类型的对象,但是目前为止没有浏览器对其进行实现)。

    localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。

    sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。

    不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。

    JSON对象提供的parse和stringify将其他数据类型转化成字符串,再存储到storage中就可以了,操作的方式:

    存:

    var obj = {"name":"xiaoming","age":"16"}

    localStorage.setItem("userInfo",JSON.stringify(obj));

    取:

    var user = JSON.parse(localStorage.getItem("userInfo"))

    删除:

    localStorage.remove("userInfo);

    清空:

    localStorage.clear();

    pnia 使用订阅机制subscribe来实现数据的持久化存储的代码如下所示。

    const instance = useMainStore();
    // 订阅数据变化,变化时存储 instance.$id 这是storeId
    instance.$subscribe((mutation, state) => {
      localStorage.setItem(instance.$id, JSON.stringify(state));
    });
    
    //init 初始的时候获取
    const val = localStorage.getItem(instance.$id);
    if (val) {
      instance.$state = JSON.parse(val);
    }

    也可以通过watch实现

    watch(
      pinia.state,
      (state) => {
        // persist the whole state to the local storage whenever it changes
        localStorage.setItem('piniaState', JSON.stringify(state))
      },
      { deep: true }
    )

    但是需要注意,这种方式持久化会提示pinia未安装挂载,所以需要在pinia挂载后再调用,这里可以将它封装成方法导出,在挂载后调用

    xport const initStore = () => {
      const instance = useMainStore();
      // 订阅数据变化,变化时存储 instance.$id 这是storeId
      instance.$subscribe((mutation, state) => {
        localStorage.setItem(instance.$id, JSON.stringify(state));
      });
    
      //init 初始的时候获取
      const val = localStorage.getItem(instance.$id);
      if (val) {
        instance.$state = JSON.parse(val);
      }
    
    }
    默认情况下,state侦听会和组件绑定在一起(如果store是在组件的setup中)。这意味着,当组件卸载时,侦听会自动被移除。如果你需要在组件被卸载时,侦听仍然保持,需要给$subscribe()方法传递第二个参数true:
    export default {
      setup() {
        const someStore = useSomeStore()
    
        // 组件卸载后,侦听也会有
        someStore.$subscribe(callback, true)
    
        // ...
      },
    }

    或者watch状态的变化

    watch(
      pinia.state,
      (state) => {
        // 在state改变时,保存在localStorage中
        localStorage.setItem('piniaState', JSON.stringify(state))
      },
      { deep: true }
    )

    3、使用pinia插件持久化存储

    pinia plugin persist官方网站:pinia-plugin-persist

    持久化存储也可以通过安装插件的方式,安装 pinia-plugin-persist 来实现。

    npm i pinia-plugin-persist --save

    使用main.js

    import { createPinia } from 'pinia'
    import piniaPluginPersist from 'pinia-plugin-persist'
    const store = createPinia()
    store.use(piniaPluginPersist)
    createApp(App).use(store).mount('#app')

    在对应的store中开启,数据默认存在 sessionStorage 里,并且会以 storeId 作为 key

    import { defineStore } from 'pinia'
    // 'main' 是storeId
    export const useMainStore = defineStore('main', {
      state: () => ({
        counter: 2,
        name: 'Eduardo',
        isAdmin: true
      }),
      // ……
      // 开启数据缓存
      persist: {
        enabled: true
      }
    })

    如果需要自定义key和存储位置,则修改参数即可。

      persist: {
        enabled: true,
        strategies: [ //使用插件自定义存储
          {
            key: 'settings', // key可以自己定义,不填的话默认就是这个store的ID
            storage: localStorage, 
          }
        ]
      },

     4、在实际项目中使用pinia

    一般项目开发,实际上存储的内容会比较多,可能根据不同的键值模块进行区分,因此把它们放在一个store/modules里面,方便的使用引用它来存取设置数据即可。

    我们这里简单以一个settings的配置信息进行介绍,其中index.ts是一个统一的创建pinia的对象并挂接到全局App上的。

    其中index.ts的代码如下所示。

    import type { App } from "vue";
    import { createPinia } from "pinia";
    import piniaPluginPersist from 'pinia-plugin-persist';//使用插件持久化
    
    const store = createPinia();
    store.use(piniaPluginPersist) //使用插件持久化
    
    export function setupStore(app: App<Element>) {
      app.use(store);
    }
    
    export { store };

    因此在main.js里面引入并挂接pinia即可。

    import { createApp } from 'vue'
    
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import 'normalize.css' // css初始化
    
    import App from './App.vue'
    import { setupStore } from "/@/store";
    
    const app = createApp(App)
    setupStore(app)
    app.use(ElementPlus)
    app.mount('#app')

    这样我们就可以再次定义一个模块化的配置信息,以便于管理存储各种不同类型的内容。

    如下面我们定义一个程序配置信息setttings.ts

    import { defineStore } from "pinia";
    import { store } from "/@/store";
    
    export type settingType = {
        title: string;
        fixedHeader: boolean;
        hiddenSideBar: boolean;
      };
    
    export const useSettingStore = defineStore({
      id: "settings",
      state: (): settingType => ({
        title: "Vue3 + TypeScript + Element",
        fixedHeader: false,
        hiddenSideBar: false
      }),
      persist: {
        enabled: true,
        strategies: [ //使用插件自定义存储
          {
            key: 'settings', // key可以自己定义,不填的话默认就是这个store的ID
            storage: localStorage, 
          }
        ]
      },
    
      getters: {
        getTitle() {
          return this.title;
        },
        getFixedHeader() {
          return this.fixedHeader;
        },
        getHiddenSideBar() {
          return this.HiddenSideBar;
        }
      },
      actions: {
        CHANGE_SETTING({ key, value }) {
          // eslint-disable-next-line no-prototype-builtins
          if (this.hasOwnProperty(key)) {
            this[key] = value;
          }
        },
        changeSetting(data) {
          this.CHANGE_SETTING(data);
        }
      }
    });
    
    export function useSettingStoreHook() {
      return useSettingStore(store);
    }

    然后在组件视图vue或者app.vue中使用即可

    <script lang="ts">
    import { defineComponent } from "vue";
    
    import { useSettingStoreHook } from "/@/store/modules/settings";
    import { storeToRefs } from "pinia";
    
    export default defineComponent({
      name: "app",
      components: {
      },
      setup() {
        const store = useSettingStoreHook();
    
        const { fixedHeader, title } = storeToRefs(store);
        return {
          fixedHeader,
          title,
        };
      },
      methods: {
        setTitle() {
          this.title = "Vue3 + TypeScript + Element + Edit";
          console.log(this.title);
        },
      },
    });
    </script>

    查看数据修改后,存储在本地存储空间中的内容,如下所示。

  • 相关阅读:
    STL Allocator
    Several NeedToKnow(assert/stdin/stdout/CString/Standard C++ Library)
    VS Project Property Sheet
    进度总结(3)
    进度总结(2)
    进度总结(4)
    进度总结(7)
    进度总结(1)
    进度总结(5)
    进度总结(6)
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/16117968.html
Copyright © 2020-2023  润新知