Vue 3 & Pinia 状态管理(3) - Pinia State 的相关使用

作者: 温新

分类: 【Vue.js】

阅读: 1642

时间: 2023-07-20 17:14:08

嗨,我是温新,一名 PHPer

本篇目标:了解什么是 Pinia

Pinia 官方文档:https://pinia.web3doc.top/introduction.html

state 是 Pinia 中的核心部分。state 是什么?把它当做属性来看,当做属性的来操作。

添加 state

添加 state 数据,只需要在 defineStore 第二个参数中添加相关数据即可。

// src/stores/user.ts

import { defineStore } from 'pinia'
import {ref} from 'vue'

// 组合式 API
export default defineStore('user', () => {
  // 添加公用数据
  const name = ref('自如初')
  const url = ref('https:/www.ziruchu.com')
  const age = ref(4)

  return {name, url, age}
})

使用 State 数据

第一种使用方式

<template>
 <!-- src/App.vue -->
  <div>
    <h2>名称:{{ name }}</h2>
    <h2>网址:{{ url }}</h2>
    <h2>年龄:{{ age }}</h2>
      
    <hr>
      
    <p>可以直接取值进行使用</p>
    <h2>名称:{{ userStore.name }}</h2>
    <h2>网址:{{ userStore.url }}</h2>
    <h2>年龄:{{ userStore.age }}</h2>
  </div>
</template>

<script setup lang="ts">

  import { ref } from 'vue'
  import useUserStore from './stores/user'


  const userStore = useUserStore()
  // 注意类型的声明
  const name = ref(userStore.name)
  const url = ref<string>(userStore.url)
  const age = ref<number>(userStore.age)
</script>

<style scoped></style>

第二种使用方式:解构赋值

<template>
  <div>
    <h2>名称:{{ name }}</h2>
    <h2>网址:{{ url }}</h2>
    <h2>年龄:{{ age }}</h2>
  </div>
</template>

<script setup lang="ts">

  import { toRefs } from 'vue'
  import useUserStore from './stores/user'

  const userStore = useUserStore()
  // 解构赋值
  const {name, url, age} = toRefs(userStore)

</script>

<style scoped></style>

注意:使用解构赋值时,若不使用 toRefs,则会失去响应式

多组件使用 state

使用 pinia 的一个重要原因就是组件间的数据共享,现在通过多组件来使用 state 数据。

步骤一:信件 child 子组件

<template>
	<!-- src/home/child.vue -->

    <div>
        <h1>我是 Child 子组件</h1>

        <h2>名称:{{ name }}</h2>
        <h2>网址:{{ url }}</h2>
        <h2>年龄:{{ age }}</h2>
        <input type="text" v-model="name">
    </div>
</template>

<script setup lang="ts">
    import useUserStore from '../stores/user'
    import { toRefs } from 'vue';

    const userStore = useUserStore()
    const {name, url, age} = toRefs(userStore)
</script>

步骤二:父组件使用使用子组件

<template>
  <div>
    <h2>名称:{{ name }}</h2>
    <h2>网址:{{ url }}</h2>
    <h2>年龄:{{ age }}</h2>

   <hr>
   <!-- 子组件 -->
   <child></child>
  </div>
</template>

<script setup lang="ts">
  import { toRefs } from 'vue'
  import useUserStore from './stores/user'

  import child from './home/child.vue'

  const userStore = useUserStore()
  const {name, url, age} = toRefs(userStore)

</script>

<style scoped></style>

通过浏览查看效果,会发现子组件和父组件使用了相同的数据。

child 组件中,使用了一个双向绑定的 input,通过child 组件中的 input 进行修改名称,会发现 App.vuechild.vue 中的数据都发生了变化。

因此可以得出结论,使用共享数据时,若使用了响应式,则使用该数据的所有组件都会受到影响。

修改 state 数据

修改数据,直接对属性进行赋值即可。

方式一:pinia 中的 storeToRefs

<template>
 <!-- App.vue  -->
  <div>
    <h2>名称:{{ name }}</h2>
    <h2>网址:{{ url }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名称</button>

   <hr>
   <child></child>
  </div>
</template>

<script setup lang="ts">
  // 引入 storeToRefs 
  import { storeToRefs } from 'pinia';
  import useUserStore from './stores/user'

  import child from './home/child.vue'

  const userStore = useUserStore()

  // storeToRefs 响应式
  const {name, url, age} = storeToRefs(userStore)

  const changeName = () => {
    userStore.name = '计算机导论'
  }
</script>

<style scoped></style>

这个案例在父组件 App.vue 中添加了一个修改名称的按钮,当点击修改时,子组件和父组件的数据都会发生变化,原因是使用了 storeToRefs,若不使用 storeToRefs ,则不会发生响应式改变。

除了使用了 pinia 中的 storeToRefs 外,还可以使用 vue 中的 toRefs

方式二:vue 中的 toRefs

<script setup lang="ts">
    // App.vue
    import { toRefs } from 'vue'
    import useUserStore from './stores/user'
    import child from './home/child.vue'

    const userStore = useUserStore()
    const {name, url, age} = toRefs(userStore)

    const changeName = () => {
    	userStore.name = '计算机导论'
    }
</script>

批量修改 state 数据

批量修改数据,使用 store 中的 $patch 方法。

为了保持整洁,重复的内容省略了。

方式一: $patch 批量修改

<!-- App.vue -->
<button @click="changePatchStore">批量修改数据</button>

<script setup lang="ts">
  const changePatchStore = () => {
    userStore.$patch({
      name: "操作系统原理",
      age: 19,
    })
  }
</script>

使用这种方法,代价有点高,任何集合修改(例如,从数组中推送、删除、拼接元素)都需要创建一个新集合。

方式二:$patch 参数进行批量修改

<script setup lang="ts">
  const changePatchStore = () => {
    userStore.$patch( state => {
      state.name = '鸟哥的私房菜',
      state.age = 8
    })
  }
</script>

方式三:替换

pinna 中提供了 $state 方法替换整个 state 对象

<script>
  const changePatchStore = () => {
    // 方式一:使用 $state 替换整个 state
    userStore.$state = {name:"算法导论", age:19, url:"www.ziruchu.com"}
    // 方式二: 使用 $patch 可以替换一个或多个 
    //userStore.$patch({name:"计算机科学导论"})
  }
</script>

重置 state

修改了 state 数据后,想要还原到旧数据时,可以使用 store$reset() 方法。

注意:按照文档说明案例,使用选项式 API 时不会出现问题,但是在使用组合式 API 时会存在问题。下面使用组合式 API 进行学习,并解决组合式 API 中 $reset 报错问题。

步骤一:添加重置功能

<!-- App.vue -->
<button @click="handleReset">重置</button>

<script>
  const handleReset = () => {
    userStore.$reset()
  }
</script>

当点击重置按钮时,组合式 API 中 $reset 报错信息如下:

runtime-core.esm-bundler.js:221 Uncaught Error:: Store "user" is built using the setup syntax and does not implement $reset().

步骤二:解决组合式 API 报错

// main.ts

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

const app = createApp(App)

// 以插件的形式为组合式 API 添加 $reset 方法
const pinia = createPinia()
pinia.use(({store}) => {
    // 获取原始值
    const initialState = JSON.parse(JSON.stringify(store.$state))
    store.$reset = () => {
        // 重置原始使用
        store.$state = JSON.parse(JSON.stringify(initialState))
    }
})
app.use(pinia)

app.mount('#app')

订阅状态

通过 store 的 $subscribe() 方法查看状态及其变化。与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次。

<script>
    // App.vue
    import { toRefs, watch } from 'vue'

    userStore.$subscribe((mutation, state) => {
    	console.log(mutation, state)
    })

    watch(userStore.$state, (newValue, oldValue) => {
    	console.log(newValue, oldValue)
    }) 
</script>

1、点击 修改名称 按钮,控制台中观察$subscribewatch 的变化;

2、再次点击 修改名称 按钮,观察两者的变化。

关于 state 的相关操作就结束了。

最后附上完整代码

<template>
<!-- App.vue -->
  <div>
    <h2>名称:{{ name }}</h2>
    <h2>网址:{{ url }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名称</button>
    <button @click="changePatchStore">批量修改数据</button>
    <button @click="handleReset">重置</button>

   <hr>
   <child></child>
  </div>
</template>

<script setup lang="ts">

  import { toRefs, watch } from 'vue'
  import useUserStore from './stores/user'

  import child from './home/child.vue'


  const userStore = useUserStore()

  const {name, url, age} = toRefs(userStore)

  const changeName = () => {
    userStore.name = '计算机导论'
  }
  // const changePatchStore = () => {
  //   userStore.$patch({
  //     name: "操作系统原理",
  //     age: 19,
  //   })
  // }


  // const changePatchStore = () => {
  //   userStore.$patch( state => {
  //     state.name = '鸟哥的私房菜',
  //     state.age = 8
  //   })
  // }

  const changePatchStore = () => {
    // userStore.$state = {name:"算法导论", age:19, url:"www.ziruchu.com"}
    userStore.$patch({name:"计算机科学导论"})
  }

  const handleReset = () => {
    userStore.$reset()
  }

  userStore.$subscribe((mutation, state) => {
    console.log(mutation, state)
  })

  watch(userStore.$state, (newValue, oldValue) => {
    console.log(newValue, oldValue)
  })

</script>

<style scoped></style>
请登录后再评论