Pinia - 更契合Vue3的状态管理库

Pinia

在Vue3中,你仍然可以使用Vuex,但是官方文档建议使用Pinaia,应用在服务端渲染时,PiniaVuex更加安全。

相比于VuexPinia少了Mutations

安装和使用

安装:

npm install pinia

使用pinia

main.js

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

核心概念

Store

Store管理着全局状态,和Vuex中一样的概念。在store中,可以通过this访问当前的store实例

请注意,每个Store都被reactive包装过,其中的响应式内容可以被自动解包。Store的实例化应该在pinia创建之后

定义Store

使用defineStore()定义,第一个参数是store的唯一ID(命名是要保证独一无二),Pinia用这个ID来连接storedevtools,第二个参数是store的配置,可以是一个Setup函数或者一个Options对象

import { defineStore } from 'pinia'
// 官方建议是对返回值用 useXxxStore 的格式进行命名
export const useMyStore = defineStore('myStrore', {
    // 像vuex中一样,引入各种各样的store模块,比如: 
    /*
    *  useUserStore,
    *  usePetStore,
    *  usePermisstionStore,
    */
})

defineStore()中可以传入一个Options对象来定义一个store,这种方式非常像Vuex

// 默认暴露
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
    state: () => ({ 
        account: 11111,
        passwords: 15555,
        score: 0,
    }),
    getters: {
        level: (state) => {
            state.score>60 ? 'A' : 'B'
        }
    },
    actions: {
        // actions中可以通过this访问本store中的数据
        addScore(){
            this.score += 10
        }
    }
})

defineStore()中可以传入一个setup函数来定义store,在这个函数中,ref()相当于statecomputed()相当于gettersfunction()相当于methods。传入创建store显然更加灵活

import { defineStore } from 'pinia'
export const uesCityStore = defineStore('city', () => {
    const peopleData = ref(0)
    const weather = ref('晴')
    function increment(){
        //使用ref()响应式声明的状态,应当使用其value属性来改变值
        peopleData.value++
    }
    
    // 声明的都要 return
    return { peopleData, weather, increment }
})

引入和使用Store

引入后需要将其实例化,store是一个reactive包裹的对象,不要直接对state解构赋值,那将破坏响应性。可以对actions直接解构

<script setup>
import { useUserStore } from '@/stores/user'
// 实例化 store
cosnt userStore = useUserStore()
// 然后在该组件中就可以通过 userStore 调用 useUserStore

setTimeOut( ()=>{
    store.addScore()
    console.log(store.score)
}, 1000)
</script>

如果想要解构复制state同时保持其响应性,那么需要调用storeToRefs()

import { storeToRefs } from 'pinia'
const { account, passwords } = storeToRefs(userStore)

State

state就像是data,Pinia中,state被定义为一个返回初始状态的函数

创建State

JS:

import { defineStore } from 'pinia', 
const useStore = defineStore('test', {
    state: () => {
        return {
            // 里面写法就和 Vuex 一样了
        }
    }
})

TS

// 使用接口定义 state, 并设置 state() 的返回值类型
// 先定义数据类型的接口
interface UserInfo {
    id: Number
    account: String
    passwords: String
}
//然后定义State的接口
interface State {
    userList: UserInfo[]
    user: UserInfo | null
}
//创建store
const useStore = defineStore('test', {
    // 使用State接口
    state: (): State => {
        return {
            userList: [],
            user: null
        }
    }
})

或者可以不定义State接口,让Pinia自动推断state的类型

interface UserInfo {
    id: Number
    account: String
    passwords: String
}

const useUserStore = defineStore('test', {
    state: () => {
        return {
            userList: [] as UserInfo[], // 设置初始值和类型
            user: null as UserInfo | null
        }
    }
})

访问State

可以直接用store访问: userStore.user

重置State

调用store$reset() 方法

userStore.$reset()

mapState()

Pinia同样支持mapState()辅助函数,它会将state映射为只读属性,使用格式和vuex一样

在组件中修改state中的状态名

使用mapWritableState()

<script setup>
import { mapWritableState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'

const msg = computed(() => {
    return {
        //这种方式引入的state状态名没有变化,可以通过 this.account 访问useUserStore中的account
        ...mapWritableState(useUserStore, ['account', 'passwords'])
        
        //这种方式下引入的state状态名发生了改变,在该组件下需要使用 this.ac 访问useUserStore中的account
        ...mapWritableState(useUserStore, {
            ac: 'account'
        })
    }
})
</script>

批量变更State状态

Pinia中,可以直接通过store变更state, 也可以使用store.$patch()进行状态的变更

变更有两种方法,第一种是传入一个配置项

userStore.$patch({
    account: '600600',
    passwords: 'asdasdads',
    id: userStore.id==1 ? 0 : 1
})

第二种是传一个方法,也是常用的

userStore.$patch((state) => {
    state.userList.push({ account: '45485454', passwords: 'asdasfas', id: 6 })
})

初始化整个app的state

pinia.state.value = {}

订阅state

使用$subscribe()方法监听state的变化,每当state发生变化时触发其中的回调。它需要接收一个带有参数为(mutation, state)的方法,和一个可选项detached来设置是否在组件被卸载后还要保留该订阅

userStore.$subscribe((mutation, state) => {
        // ...
    },
    { detached: true }
)

Getter

Getter就像是computed,你需要使用getters属性去定义

创建和访问

你可以使用箭头函数或者匿名函数去创建

export const userStore = defineStore('test', {
    getters: {
        newId: (state) => {
            state.id = 30
        },
        role(){
            // 使用 this 访问同一 store 中的其他 getter
            return this.newId%2==0 ? 'admin' : ''
        },
        // TS中必须定义返回类型
        newIdByTs(): number {
            return this.id++
        }
    }
})

组件中访问: userStore.newId

可以在组件中给getter传参

<script setup>
import { useUserListStore } from './store'
const user = useUserStore()
const { getUserId } = storeToRefs(user)
// 请注意,你 应该使用 `getUserId.value` 来访问 <script setup> 中的函数
</script>

<template>
  <p>UserId: {{ getUserId(6) }}</p>
</template>

在getter中缓存结果

getters中缓存结果,有时可以提高getter的性能

export const useUserStore = defineStore('test', {
  getters: {
    getActiveUserById(state) {
      const xxx // xxx是缓存在getter中的东西
      return func(xxx)
    },
  },
})

在getter中访问其他store的getter

把别的store import进来实例化然后正常用就行了

import { otherStore } from 'xxx'

export const userStore = defineStore('test', {
    getters: {
        otherGetter: (state) => {
            const otherStore = otherStore()
            
            // ...正常用就行
        }
    }
})

Action

actions就像是methods

定义和调用

创建和Vuex一样,里面的函数也是,actions中的方法都可以是异步的,在里面可以使用await、this

调用actions中的方法

// 不论是js中还是模板中都是
yourStoreName.functionsInActions(params)

访问其他store中的actions

类比在getters中调用其他store的getter

订阅actions

使用stiore.$onAction(callback, Boolean)监听actions的结果并执行回调。他有几个回调的执行时刻:

  • after: 在Promise解决后,允许在action解决后执行一个回调
  • onError: 允许在action抛出错误或者 Promise reject 时执行一个回调
import { yourStore } from 'xxx'
const yourStore = yourStore()
// 在store实例上绑定一个用于当前组价中的actions监听器
const unsubscribe = yourStore.$onAction(
    (params) => {
        // 执行一些回调,总会触发
        
        //当action成功运行完成后触发,after()接收action返回的任何promise
        after((result) => {
            // ...
        })
        
        //若action抛出一个错误或者返回一个reject的Promise时触发
        onError((err) => {
            // ...
        })
    },
    true //可选项, 设置true, 那么在使用这个订阅器的组件卸载后,该订阅器依然会被保留
)

// 移除监听
unsubscribe()

Pinia 扩展插件

我们可以写一个函数将他作为插件,然后使用pinia.use()全局挂载到Pinia实例上,然后他会作为每一个store的新属性而存在。在一个插件中, state 变更或添加(包括调用 store.$patch())都是发生在 store 被激活之前,因此不会触发任何订阅函数

import { createPinia } from 'pinia'
const pinia = createPinia()

function myPlugin(){
    // ...
    return ...
}

// 全局挂载插件
pinia.use(myPlugin)

// 或者这样,建议传入对象,这杨能够被devtools自动追踪到
pinia.use(({ params }) => {
    // ...
})


// 其他文件中使用
const yourStore = youStore()
yourStore.myPlugin()

添加新的外部属性

当添加外部属性、第三方库的类实例或者非响应式的简单值时,需要先用vue提供的markRaw()将其包装,再将它传给pinia

import { markRaw } from 'vue'
import { yyy } from 'xxx'  //外部属性、第三方库的类实例或者非响应式的简单值

pinia.use(({ store }) => {
    store.yyy = markRaw(yyy)
})

插件中可以使用store.$subscribe()store.$onAction()

添加新选项

定义store时可以创建新选项,创建的新选项会被插件读取并使用

创建一个防抖选项实现所有的action防抖

const yourStore = defineStore('storeId', {
    actions: {
        sendMsg(){
            // ...
        }
    },
    debounce: {
        sendMsg: 400, // 让sendMsg这个action防抖400ms
    }
})
// 使用任意防抖库
import debounce from 'lodash/debounce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // 用新的 action 来覆盖旧的action
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})

TS中使用

组件外的Store

单页面应用中使用

如果想在路由器中使用store,那么要把store实例化放在router.beforeEach()内,因为那个时候Pinia已经被安装。由此可知,你应该在Pinia被安装后再实例化store

SSR —— 服务端渲染

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

六时二一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值