Pinia学习

Vue3中 使用

官网:https://pinia.web3doc.top/introduction.html

安装

yarn add pinia
# 或者使用 npm
npm install pinia

使用

main.js中引入并注册

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

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

src下创建store文件夹-用于管理state

// 命名规范 
// use + 逻辑名 || 业务名 + store
// useCounterStore

1706169098070

Store

创建

import { defineStore } from 'pinia'
// defineStore    
// 参数一是一个id 参数二是配置项  state   getters actions
export const useCounterStore = defineStore('counter', {
  state: () => {
    return {
      count: 0
    }
  },
  getters: {
    doubleCount: () => state.count * 2
  },
  actions: {
    increment () {
      this.count++
    },
    decrement () {
      this.count--
    }
  }
})

Store的 使用

<script setup>
import { useCounterStore } from './store/useCounterStore'
import { storeToRefs } from 'pinia'

// store 是一个用reactive 包裹的对象
// 这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构:
const store = useCounterStore()
// 想结构后保持其响应式 就必须借用 storeToRefs()
const { count } = storeToRefs(store)
console.log('我创建的第一个仓库', store);
console.log('仓库结构', count); // ref包裹的对象
</script>

Store知识点

  1. defineStore 创建仓库

参数1 id 参数2 配置项

返回值 reactive包裹的响应式对象

  1. 引入 ==》 调用 ==》 结构

    import { useCounterStore } from './store/useCounterStore'

    import { storeToRefs } from 'pinia'

    const store = useCounterStore()

    const { count } = storeToRefs(store)

  2. 解构时 直接结构调用后的返回值(store)得到的是不具备响应式的数据

    需要通过storeToRefs () 包裹 store 解构出来的数据才具备响应式 (ref)

State

创建

export const useCounterStore = defineStore("counter", {
  state: () => {
    return {
      num: 0,
      count:0
    };
  }
  })

访问状态

默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态:

<script setup>
import { useCounterStore } from './store/useCounterStore'
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count } = storeToRefs(store)
console.log('我创建的第一个仓库', store);
console.log('仓库结构', count); // ref包裹的对象

重置状态

原理

调用的$patch

const $reset = isOptionsStore

​ ? function $reset(this: _StoreWithState<Id, S, G, A>) {

​ const { state } = options as DefineStoreOptions<Id, S, G, A>

​ const newState = state ? state() : {}

​ // we use a patch to group all changes into one single subscription

​ this.$patch(($state) => {

​ assign($state, newState)

​ })

​ }

​ : /* istanbul ignore next */

DEV

​ ? () => {

​ throw new Error(

🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().

​ )

​ }

​ : noop

你可以使用store中的 $reset 进行状态重置

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

使用选项API

// 仓库文件
import { defineStore } from "pinia";

// 第二个参数支持两种风格:options api 以及 composition api
export const useCounterStore = defineStore("counter", {
  state: () => {
    return {
      num: 0,
    };
  }
})

组合API

import { defineStore } from "pinia";
import { reactive } from "vue";
export const useListStore = defineStore("list", () => {
  const counterStore = useCounterStore();
    // 组合 api 风格

  // 创建仓库数据,类似于 state
  const list = reactive({
    items: [
      {
        text: "学习 Pinia",
        isCompleted: true,
      },
      {
        text: "打羽毛球",
        isCompleted: false,
      },
      {
        text: "玩游戏",
        isCompleted: true,
      },
    ],
    counter: 100,
  });
  return {
    list
  }
})

非组合API

如果您不使用 Composition API,并且使用的是 computedmethods、...,则可以使用 mapState() 帮助器将状态属性映射为只读计算属性:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // 允许访问组件内部的 this.counter
    // 与从 store.counter 读取相同
    ...mapState(useCounterStore, {
      myOwnName: 'counter',
      // 您还可以编写一个访问 store 的函数
      double: store => store.counter * 2,
      // 它可以正常读取“this”,但无法正常写入...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

可修改状态 mapWritableState

如果您希望能够写入这些状态属性(例如,如果您有一个表单),您可以使用 mapWritableState() 代替。 请注意,您不能传递类似于 mapState() 的函数:

import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // 允许访问组件内的 this.counter 并允许设置它
    // this.counter++
    // 与从 store.counter 读取相同
    ...mapWritableState(useCounterStore, ['counter'])
    // 与上面相同,但将其注册为 this.myOwnName
    ...mapWritableState(useCounterStore, {
      myOwnName: 'counter',
    }),
  },
}

对于像数组这样的集合,您不需要 mapWritableState(),除非您用 cartItems = [] 替换整个数组,mapState() 仍然允许您调用集合上的方法。

改变状态

方法一: 直接通过仓库实例更改

方法二: 通过$patch

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

但是,使用这种语法应用某些突变非常困难或代价高昂:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。 正因为如此,$patch 方法也接受一个函数来批量修改集合内部分对象的情况:

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

替换state

您可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态:

store.$state = { counter: 666, name: 'Paimon' }

您还可以通过更改 pinia 实例的 state 来替换应用程序的整个状态。 这在 SSR for hydration 期间使用。

pinia.state.value = {}

订阅状态

可以通过 store 的 $subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次(例如,当使用上面的函数版本时)。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 与 cartStore.$id 相同
  mutation.storeId // 'cart'
  // 仅适用于 mutation.type === 'patch object'
  mutation.payload // 补丁对象传递给 to cartStore.$patch()

  // 每当它发生变化时,将整个状态持久化到本地存储
  localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,state subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 当前组件的 state subscription

export default {
  setup() {
    const someStore = useSomeStore()

    // 此订阅将在组件卸载后保留
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

您可以在 pinia 实例上查看整个状态:

watch(
  pinia.state,
  (state) => {
    // 每当它发生变化时,将整个状态持久化到本地存储
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

状态持久化

数据持久化的实现原理:
利用localStorage存储到本地

import { defineStore } from 'pinia'

const useStateStore = defineStore('state', {

  state: () => {
    return {
      a: 1,
      b: 2,
      c: false
    }
  },
  persist: true, // 持久化配置
})
export default useStateStore
// 安装 插件  
// pinia-plugin-persistedstate
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

Getters

本质:computed计算属性

  1. getters语法和state一样

  2. getters 中可以接收state作为参数

  3. getters中如果要使用其他getters 不能继续使用箭头函数

  4. 使用其他store中的数据 直接在当前store引入即可

定义

export const useStore = defineStore('store', {
    state:() =>{
        a:1
    }
	getters:{
		double:(state) =>{
			return state.a +1
		}
	}
})

this

大多数时候,getter 只会依赖状态,但是,他们可能需要使用其他 getter。 正因为如此,我们可以在定义常规函数时通过 this 访问到 整个 store 的实例

但是需要定义返回类型(在 TypeScript 中)。 这是由于 TypeScript 中的一个已知限制,并且不会影响使用箭头函数定义的 getter,也不会影响不使用 this 的 getter

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 自动将返回类型推断为数字
    doubleCount(state) {
      return state.counter * 2
    },
    // 返回类型必须明确设置
    doublePlusOne(): number {
      return this.counter * 2 + 1
    },
  },
})

使用

直接在store实例上进行调用即可

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>
// 写法一
<script>
export default {
  setup() {
    const store = useStore()

    return { store }
  },
}
</script>
// 写法二
<script setup>
export default {
    const store = useStore()
}
</script>

访问其他getter

当前getter要访问其他getter,可以借助普通函数中的this

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 类型是自动推断的,因为我们没有使用 `this`
    doubleCount: (state) => state.counter * 2,
    // 这里需要我们自己添加类型(在 JS 中使用 JSDoc)。 我们还可以
    // 使用它来记录 getter
    /**
     * 返回计数器值乘以二加一。
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // 自动完成 ✨
      return this.doubleCount + 1
    },
  },
})

参数传递

由于getter本质上也是计算属性,因此它本身不接收任何参数,但是我们可以通过返回函数的形式让其接收参数

export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})
实际使用

缺点:getter不再缓存

<script setup>
import {storeToRefs} from 'pinia'
export default {
    const store = useStore()
	const {getUserById} = storeToRefs(store)
}
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

Actions

Actions 相当于组件中的 methods。 它们可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑

定义

getters 一样,操作可以通过 this 访问 whole store instance 并提供完整类型(和自动完成✨)支持与它们不同,actions 可以是异步的

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

使用 setup

<script setup>
    const useStore = useStore()
 	// 像methods一样被调用即可    
	useStore.increment()
</script>

不适用setup

import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  methods: {
    // gives access to this.increment() inside the component
    // same as calling from store.increment()
    ...mapActions(useCounterStore, ['increment'])
    // same as above but registers it as this.myOwnName()
    ...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
  },
}

访问其他store

直接在内部引入使用即可

mport { useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
  state: () => ({
    // ...
  }),
  actions: {
    async fetchUserPreferences(preferences) {
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})

订阅

可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数。 以类似的方式,onError 允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用,类似于 Vue 文档中的这个提示

const unsubscribe = someStore.$onAction(
  ({
    name, // action 的名字
    store, // store 实例
    args, // 调用这个 action 的参数
    after, // 在这个 action 执行完毕之后,执行这个函数
    onError, // 在这个 action 抛出异常的时候,执行这个函数
  }) => {
    // 记录开始的时间变量
    const startTime = Date.now()
    // 这将在 `store` 上的操作执行之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 如果 action 成功并且完全运行后,after 将触发。
    // 它将等待任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 如果 action 抛出或返回 Promise.reject ,onError 将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动移除订阅
unsubscribe()

插件Plugins

插件仅适用于**在将pinia传递给应用程序后创建的 store **,否则将不会被应用。

作用

  • 向 Store 添加新属性
  • 定义 Store 时添加新选项
  • 为 Store 添加新方法
  • 包装现有方法
  • 更改甚至取消操作
  • 实现本地存储等副作用
  • 适用于特定 Store

基本用法

import { createPinia } from 'pinia'

// 为安装此插件后创建的每个store添加一个名为 `secret` 的属性
// 这可能在不同的文件中
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// 将插件提供给 pinia
pinia.use(SecretPiniaPlugin)

// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'

参数

Pinia 插件是一个函数,可以选择返回要添加到 store 的属性。 它需要一个可选参数,一个 context

export function myPiniaPlugin(context) {
  context.pinia // 使用 `createPinia()` 创建的 pinia
  context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
  context.store // 插件正在扩充的 store
  context.options // 定义存储的选项对象传递给`defineStore()`
  // ...
}

pinia.use(myPiniaPlugin)

扩充store

插件的任何属性 returned 都会被devtools自动跟踪,所以为了让hello在devtools中可见,如果你想调试它,请确保将它添加到store._customProperties仅在开发模式 开发工具:

// 从上面的例子
pinia.use(({ store }) => {
  store.hello = 'world'
  // 确保您的打包器可以处理这个问题。 webpack 和 vite 应该默认这样做
  if (process.env.NODE_ENV === 'development') {
    // 添加您在 store 中设置的任何 keys
    store._customProperties.add('hello')
  }
})

请注意,每个 store 都使用 reactive 包装,自动展开任何 Ref (ref(), computed() , ...) 它包含了:

这就是为什么您可以在没有 .value 的情况下访问所有计算属性以及它们是响应式的原因。

const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // 每个 store 都有自己的 `hello` 属性
  store.hello = ref('secret')
  // 它会自动展开
  store.hello // 'secret'

  // 所有 store 都共享 value `shared` 属性
  store.shared = sharedRef
  store.shared // 'shared'
})

添加新状态

请注意,插件中发生的状态更改或添加(包括调用store.$patch())发生在存储处于活动状态之前,因此不会触发任何订阅

  • store 上,因此您可以使用 store.myState 访问它
  • store.$state 上,因此它可以在 devtools 中使用,并且在 SSR 期间被序列化
onst globalSecret = ref('secret')
pinia.use(({ store }) => {
  // `secret` 在所有 store 之间共享
  store.$state.secret = globalSecret
  store.secret = globalSecret
  // 它会自动展开
  store.secret // 'secret'

  const hasError = ref(false)
  store.$state.hasError = hasError
  // 这个必须始终设置
  store.hasError = toRef(store.$state, 'hasError')

  // 在这种情况下,最好不要返回 `hasError`,因为它
  // 将显示在 devtools 的 `state` 部分
  // 无论如何,如果我们返回它,devtools 将显示它两次。
})

插件中使用订阅

pinia.use(({ store }) => {
  store.$subscribe(() => {
    // 在存储变化的时候执行
  })
  store.$onAction(() => {
    // 在 action 的时候执行
  })
})