6

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

 2 years ago
source link: https://www.cnblogs.com/wuhuacong/p/16117968.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在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>

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

8867-20220408164902287-283961117.png
主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
  转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com 

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK