Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API 。从那时起,最初的原则仍然相同,但 Pinia 对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。 除了安装和 SSR 之外,两者的 API 都是相同的,并且这些文档针对 Vue 3,并在必要时提供有关 Vue 2 的注释,以便 Vue 2 和 Vue 3 用户可以阅读!
为什么要使用 Pinia?#
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:
-
dev-tools 支持
- 跟踪动作、突变的时间线
- Store 出现在使用它们的组件中
- time travel 和 更容易的调试
-
热模块更换
- 在不重新加载页面的情况下修改您的 Store
- 在开发时保持任何现有状态
-
插件:使用插件扩展 Pinia 功能
-
为 JS 用户提供适当的 TypeScript 支持或 autocompletion
-
服务器端渲染支持
与 Vuex 的比较#
Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。
安装
安装
yarn add pinia
# 或者使用 npm
npm install pinia
引入并挂载
import { createPinia } from 'pinia'
app.use(createPinia())
什么是 Store ?#
一个 Store (如 Pinia)是一个实体,它持有未绑定到您的组件树的状态和业务逻辑。换句话说,它托管全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。它有三个概念,state、getters 和 actions 并且可以安全地假设这些概念等同于组件中的“数据”、“计算”和“方法”。
我什么时候应该使用 Store
存储应该包含可以在整个应用程序中访问的数据。这包括在许多地方使用的数据,例如导航栏中显示的用户信息,以及需要通过页面保留的数据,例如一个非常复杂的多步骤表格。
另一方面,您应该避免在存储中包含可以托管在组件中的本地数据,例如页面本地元素的可见性。
并非所有应用程序都需要访问全局状态,但如果您需要一个,Pania 将使您的生活更轻松。
定义一个 Store
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西(相当于名称,推荐use开头)
// 第一个参数是应用程序中 store 的唯一 id(必填项)
export const useStore = defineStore('main', {
// other options...
})
这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。
使用 store#
我们正在 定义 一个 store,因为在 setup() 中调用 useStore() 之前不会创建 store:
import { useStore } from '@/stores/counter'//引入
const store = useStore()//定义一个仓库
store 被实例化,你就可以直接在 store 上访问 `state`、`getters` 和 `actions` 中定义的任何属性。
store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构:
错误写法,会失去响应式
const { name, doubleCount } = store
使用storeToRefs()的写法,数据保持响应式,但无法修改
const { name, doubleCount } = storeToRefs(store)
访问 “state”#
一般情况下可以通过store 实例直接读取和写入状态:
const store = useStore()
store.counter++
重置状态#
您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:
const store = useStore()
store.$reset()
除了直接用 store.counter++ 修改 store,你还可以调用 $patch 方法。 它允许您使用部分“state”对象同时应用多个更改:
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
但是,使用这种语法应用某些突变非常麻烦:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要创建一个新集合。 正因为如此,$patch 方法也接受一个函数来批量修改集合内部分对象的情况:
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
geeters属性(案例)
App.vue
<template>
<header>
<div>
<div>
<!-- 页面上可以直接使用 -->
这是年龄--{{ userStore.age }} 双倍年龄--{{ userStore.getNameAdnAge }}
<button @click="add">+1</button>
</div>
<div>
<!-- 页面上使用的时候,需要进行传参 -->
这个是天数{{ userStore.getAddAge(0) }}
<button @click="add1">+1</button>
</div>
</div>
</header>
</template>
<script setup lang="ts">
// 引入定义的userStore模块
import { storeToRefs } from "pinia";
import { useUserStore } from "./stores/counter";
// 使用userStore 并返回定义模块的实例
const userStore = useUserStore();
/**
* 每次修改计算属性里所依赖的值
* 计算属性会自动的重新计算
*/
const add = () => {
userStore.age += 1;
};
const add1 = () => {
userStore.day += 365;
};
</script>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
stores下的counter.ts
import { ref, computed } from "vue";
// 引入defineStore 方法
import { defineStore } from "pinia";
/**
* pinia中使用defineStore定义store
* 第一个参数是应用程序中 store 的唯一 id
* 第二个参数是是一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
*
* 返回一个函数使用use+模块名命名
*/
export const useUserStore = defineStore("user", {
// 其它配置项
/**
* 定义当前的模块的state
* state是一个函数,必须有返回值
* 返回值就是这个模块的状态
* @returns
*/
state() {
// 返回age为1
return {
/**
* 年龄
*/
age: 18,
day: 0,
name: "=>意会",
};
},
/**
* 计算属性在getters对象中定义
* 定义一个方法,方法必须有返回值,返回的值就是这个数算属性的结果
*
* 方法接收一个参数,为当前的state
*/
getters: {
// 两倍年龄
doubleAge: (state) => state.age * 2,
getNameAdnAge(): string {
return this.doubleAge + this.name;
},
getAddAge () {
/**
* 在计算属性内部,返回一个方法
* 该方法会接收一个参数,这个参数就是
* 页面传过来的参数
*/
return (num: number) => this.day + num;
}
},
});