Vuex 作为一个老牌 Vue 状态治理库,大家都很相熟了
Pinia 是 Vue.js 团队成员专门为 Vue 开发的一个全新的状态治理库,并且曾经被纳入官网 github
为什么有 Vuex 了还要再开发一个 Pinia ?
先来一张图,看下过后对于 Vuex5 的提案,就是下一代 Vuex5 应该是什么样子的
Pinia 就是残缺的合乎了他过后 Vuex5 提案所提到的性能点,所以能够说 Pinia 就是 Vuex5 也不为过,因为它的作者就是官网的开发人员,并且曾经被官网接管了,只是目前 Vuex 和 Pinia 还是两个独立的仓库,当前可能会合并,也可能独立倒退,只是官网必定举荐的是 Pinia
因为在 Vue3 中应用 Vuex 的话须要应用 Vuex4,并且还只能作为一个过渡的抉择,存在很大缺点,所以在 Componsition API 诞生之后,也就设计了全新的状态治理 Pinia
Pinia 和 Vuex
Vuex: State
、Gettes
、Mutations
(同步)、Actions
(异步)
Pinia: State
、Gettes
、Actions
(同步异步都反对)
Vuex 以后最新版是 4.x
- Vuex4 用于 Vue3
- Vuex3 用于 Vue2
Pinia 以后最新版是 2.x
- 即反对 Vue2 也反对 Vue3
就目前而言 Pinia 比 Vuex 好太多了,解决了 Vuex 的很多问题,所以笔者也十分倡议间接应用 Pinia,尤其是 TypeScript 的我的项目
Pinia 外围个性
- Pinia 没有
Mutations
Actions
反对同步和异步没有模块的嵌套构造
- Pinia 通过设计提供扁平构造,就是说每个 store 都是相互独立的,谁也不属于谁,也就是扁平化了,更好的代码宰割且没有命名空间。当然你也能够通过在一个模块中导入另一个模块来隐式嵌套 store,甚至能够领有 store 的循环依赖关系
更好的
TypeScript
反对- 不须要再创立自定义的简单包装器来反对 TypeScript 所有内容都类型化,并且 API 的设计形式也尽可能的应用 TS 类型推断
- 不须要注入、导入函数、调用它们,享受主动补全,让咱们开发更加不便
- 无需手动增加 store,它的模块默认状况下创立就主动注册的
Vue2 和 Vue3 都反对
- 除了初始化装置和SSR配置之外,两者应用上的API都是雷同的
反对
Vue DevTools
- 跟踪 actions, mutations 的工夫线
- 在应用了模块的组件中就能够察看到模块自身
- 反对 time-travel 更容易调试
- 在 Vue2 中 Pinia 会应用 Vuex 的所有接口,所以它俩不能一起应用
- 然而针对 Vue3 的调试工具反对还不够完满,比方还没有 time-travel 性能
模块热更新
- 无需从新加载页面就能够批改模块
- 热更新的时候会放弃任何现有状态
- 反对应用插件扩大 Pinia 性能
- 反对服务端渲染
Pinia 应用
以 Vue3 + TypeScript
为例
装置
npm install pinia
main.ts
初始化配置
import { createPinia } from 'pinia'createApp(App).use(createPinia()).mount('#app')
在 store 目录下创立一个 user.ts
为例,咱们先定义并导出一个名为 user
的模块
import { defineStore } from 'pinia'export const userStore = defineStore('user', { state: () => { return { count: 1, arr: [] } }, getters: { ... }, actions: { ... }})
defineStore
接管两个参数
第一个参数就是模块的名称,必须是惟一的,多个模块不能重名,Pinia 会把所有的模块都挂载到根容器上
第二个参数是一个对象,外面的选项和 Vuex 差不多
- 其中
state
用来存储全局状态,它必须是箭头函数,为了在服务端渲染的时候防止穿插申请导致的数据状态净化所以只能是函数,而必须用箭头函数则为了更好的 TS 类型推导 getters
就是用来封装计算属性,它有缓存的性能actions
就是用来封装业务逻辑,批改 state
拜访 state
比方咱们要在页面中拜访 state 里的属性 count
因为 defineStore
会返回一个函数,所以要先调用拿到数据对象,而后就能够在模板中间接应用了
<template> <div>{{ user_store.count }}</div></template><script lang="ts" setup>import { userStore } from '../store'const user_store = userStore()// 解构// const { count } = userStore()</script>
比方像正文中的解构进去应用,是齐全没有问题的,只是留神了,这样拿到的数据不是响应式的,如果要解构还放弃响应式就要用到一个办法 storeToRefs()
,示例如下
<template> <div>{{ count }}</div></template><script lang="ts" setup>import { storeToRefs } from 'pinia'import { userStore } from '../store'const { count } = storeToRefs(userStore)</script>
起因就是 Pinia 其实是把 state 数据都做了 reactive
解决,和 Vue3 的 reactive 同理,解构进去的也不是响应式,所以须要再做 ref
响应式代理
getters
这个和 Vuex 的 getters 一样,也有缓存性能。如下在页面中屡次应用,第一次会调用 getters,数据没有扭转的状况下之后会读取缓存
<template> <div>{{ myCount }}</div> <div>{{ myCount }}</div> <div>{{ myCount }}</div></template>
留神两种办法的区别,写在正文里了
getters: { // 办法一,接管一个可选参数 state myCount(state){ console.log('调用了') // 页面中应用了三次,这里只会执行一次,而后缓存起来了 return state.count + 1 }, // 办法二,不传参数,应用 this // 然而必须指定函数返回值的类型,否则类型推导不进去 myCount(): number{ return this.count + 1 }}
更新和 actions
更新 state 里的数据有四种办法,咱们先看三种简略的更新,阐明都写在正文里了
<template> <div>{{ user_store.count }}</div> <button @click="handleClick">按钮</button></template><script lang="ts" setup>import { userStore } from '../store'const user_store = userStore()const handleClick = () => { // 办法一 user_store.count++ // 办法二,须要批改多个数据,倡议用 $patch 批量更新,传入一个对象 user_store.$patch({ count: user_store.count1++, // arr: user_store.arr.push(1) // 谬误 arr: [ ...user_store.arr, 1 ] // 能够,然而还得把整个数组都拿进去解构,就没必要 }) // 应用 $patch 性能更优,因为多个数据更新只会更新一次视图 // 办法三,还是$patch,传入函数,第一个参数就是 state user_store.$patch( state => { state.count++ state.arr.push(1) })}</script>
第四种办法就是当逻辑比拟多或者申请的时候,咱们就能够封装到示例中 store/user.ts 里的 actions 里
能够传参数,也能够通过 this.xx 能够间接获取到 state 里的数据,须要留神的是不能用箭头函数定义 actions,不然就会绑定内部的 this 了
actions: { changeState(num: number){ // 不能用箭头函数 this.count += num }}
调用
const handleClick = () => { user_store.changeState(1)}
反对 VueDevtools
关上开发者工具的 Vue Devtools
就会发现 Pinia,而且能够手动批改数据调试,十分不便
模仿调用接口
示例:
咱们先定义示例接口 api/user.ts
// 接口数据类型export interface userListType{ id: number name: string age: number}// 模仿申请接口返回的数据const userList = [ { id: 1, name: '张三', age: 18 }, { id: 2, name: '李四', age: 19 },]// 封装模仿异步成果的定时器async function wait(delay: number){ return new Promise((resolve) => setTimeout(resolve, delay))}// 接口export const getUserList = async () => { await wait(100) // 提早100毫秒返回 return userList}
而后在 store/user.ts 里的 actions 封装调用接口
import { defineStore } from 'pinia'import { getUserList, userListType } from '../api/user'export const userStore = defineStore('user', { state: () => { return { // 用户列表 list: [] as userListType // 类型转换成 userListType } }, actions: { async loadUserList(){ const list = await getUserList() this.list = list } }})
页面中调用 actions 发动申请
<template> <ul> <li v-for="item in user_store.list"> ... </li> </ul></template><script lang="ts" setup>import { userStore } from '../store'const user_store = userStore()user_store.loadUserList() // 加载所有数据</script>
跨模块批改数据
在一个模块的 actions 里须要批改另一个模块的 state 数据
示例:比方在 chat 模块里批改 user 模块里某个用户的名称
// chat.tsimport { defineStore } from 'pinia'import { userStore } from './user'export const chatStore = defineStore('chat', { actions: { someMethod(userItem){ userItem.name = '新的名字' const user_store = userStore() user_store.updateUserName(userItem) } }})
user 模块里
// user.tsimport { defineStore } from 'pinia'export const userStore = defineStore('user', { state: () => { return { list: [] } }, actions: { updateUserName(userItem){ const user = this.list.find(item => item.id === userItem.id) if(user){ user.name = userItem.name } } }})
结语
如果本文对你有一点点帮忙,点个赞反对一下吧,你的每一个【赞】都是我创作的最大能源,感激反对 ^_^
扫码关注公众号,即可加我好友,我拉你进前端交换群,大家一起独特交换和提高呀