Pinia初探(Vue3版本)

945 阅读3分钟

1.什么是Pinia

  • Pinia是Vuex核心成员所开发出来的一款极其接近Vuex理念的轻量级状态管理库,也是Vuex@5版本的设计思想
  • 它允许跨组件/页面共享状态
  • Pinia 适用于 Vue 2 和 Vue 3,并且不需要你一定使用组合 API

2. 为什么使用Pinia

  • 在使用Vuex时我们需要做一些事情:

    1. 修改state的值时,如果是同步更新需要通过Mutations,异步更新需要通过Actions
    2. 需要在Vuex的中使用Typescript时, 尤其是为state添加TypeScript,需要单独配置,但这并不容易, 因为目前Vuex对TypeScript的支持并不好,其中会遇到一系列的问题
    3. Vuex的模块化需要使用module
  • 使用Pinia则不需要做以上处理和面对TypeScript的支持问题:

    1. Pinia并不是Vuex的替代品,而是另一种解决方案,他们的使用场景不同
    2. Pinia具有完整的TypeScript支持
    3. 体量小(体积约1kb)
    4. Store中的Actions既可以执行同步方法也可以执行异步方法
    5. 模块不需要嵌套,可以声明多个Store
    6. 支持Vue DevTools, SSR和webpack代码拆分

3. Vuex和Pinia的选择

  • Pinia体积小,轻量,适用于中小型项目,项目大且复杂的选择Vuex
  • Pinia中对state的修改不对同步或异步进行单独区分
  • Pinia对TypeScript的支持比Vuex更好, 不需要单独配置,Vuex本身不支持TypeScript需要单独配置

4.安装Pinia

# npm
npm install pinia

# yarn
yarn add pinia

5. 在vue项目中引入Pinia(默认使用Vue3项目)

# vue3 + vite

import { createApp } from 'vue' // 引入vue
import App from './App.vue'
import { createPinia } from 'pinia' // 引入Pinia的创建函数

const app = createApp(App) // 创建Vue
app.use(createPinia()) // 向Vue中安装Pinia
app.mount('#app') // 挂载Vue到页面
# 如果您使用的是 Vue 2,您还需要安装一个插件并pinia在应用程序的根目录注入创建的插件

import Vue from 'vue'
import App from './App.vue'
import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
  el: '#app',
  pinia,
  render: (h) => h(App)
})

6.Store

6.1 什么是Store

Store是一个实体,它持有未绑定到您的组件树的状态和业务逻辑。换句话说,它托管全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。它包含三个概念状态getter动作,并且可以安全地假设这些概念等同于data,computedmethods在组件中 — Pinia官网

6.2 创建并访问Store

# Store/index.js

import { defineStore } from 'pinia' // 引入Pinia Store
export const useStore = defineStore('main', { // main 为标识id,是必须的参数,Pania 使用它来将Store连接到 devtools
  // 声明state
  state: () => ({
    count: 0
  })
})
# App.vue

<script setup>
import { useStore } from './store/index'
const store = useStore()
</script>

<template>
<div>{{ store.count }}</div>
</template>

7. State

7.1 声明state

import { defineStore } from 'pinia'

export const useStore = defineStore('main', { 
  // 声明state
  state: () => ({
    count: 0
  })
  // 或者
  // state: () => {
  //  return { count: 0 }
  //}
})

7.2 访问state

# 错误写法

<script setup>
import { useStore } from './store/index'
const { count } = store // 直接对数据进行解构,这是一种错误的写法,会破坏数据的响应式
</script> 

<template>
<div>{{ count }}</div>
</template>
# 使用storeToRefs

<script setup>
import { useStore } from './store/index'
const { count } = storeToRefs(useStore())
// 可以使用storeToRefs从存储中提取属性同时保持其反应性
// 但是仅限于从中获取响应式的属性而且不对其进行操作,不包括actions中的函数
</script> 

<template>
<div>{{ count }}</div>
</template>

8. 修改State

8.1 直接通过Store修改state

<script setup>
import { useStore } from './store/index'
const store = useStore()
console.log('===', store)
const add = () => {
  store.count++
}
</script>

<template>
<div>count: {{ store.count }}</div>
<button @click="add">add</button>
<button @click="reset">add</button>
</template>

8.2 重置state

  • 可以使用$reset,将数据恢复为初始状态
<script setup>
import { useStore } from './store/index'
const store = useStore()
console.log('===', store)
const add = () => {
  store.count++
}
const reset = () => {
  store.$reset()
}
</script>

<template>
<div>count: {{ store.count }}</div>
<button @click="add">add</button>
<button @click="reset">reset</button>
</template>

8.3 $patch修改state

  • 可以通过$patch对state进行修改,允许对部分state对象同时多个修改
# 声明state
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 1,
    name: '张三'
  })
})
# 修改state

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

const store = useStore()
const { count } = storeToRefs(useStore())
console.log('count', count)
console.log('store.count', store.count)
const changeCount = () => {
  store.$patch({
    count: store.count + 10, // 这里使用store获取state的值
    name: '李四'
  })
}
</script>

使用store获取state的值的原因是因为:

  1. storeToRefs 返回的值其实是一个对象,参与计算会出现问题
  2. 通过store访问对应值仅仅是值本身

8.4 $patch修改基本数据类型的数组

# 定义state
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    items: ['1', '2']
  })
})
# 修改数组
<script setup>
import { useStore } from './store/index'
const store = useStore()
const changeItems = () => {
  store.$patch(state => {
    state.items.push('3')
  })
}
const changeItemsHead = () => { // 可以直接通过索引进行修改
  store.$patch(state => {
    state.items[0] = 20
  })
}
</script>
<template>
<div v-for="(item, index) in store.items" :key="index">{{ item }}</div>
<button @click="changeItems">changeItems</button>
<button @click="changeItemsHead">changeItemsHead</button>
# 定义state

import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    items: [
      { name: '张三', age: 20 },
      { name: '李四', age: 23 }
    ]
  })
})
# 修改state
<script setup>
import { useStore } from './store/index'

const store = useStore()
const changeItems = () => {
  store.$patch(state => {
    state.items.push({ name: '王五', age: 30 })
  })
}
</script>

<template>
<div v-for="(item, index) in store.items" :key="index">{{ item.name }} -- {{item.age}}</div>
<button @click="changeItems">changeItems</button>

</template>
  • 但是以上的写法存在一个问题:通过patch对store中的state直接进行修改,会提高成本,甚至会在某些情况下出现问题,所以Pinia的patch方法还接受一个函数来应对这种问题
# 只需要在修改后通知Pinia,已经进行了修改
<script setup>
import { useStore } from './store/index'

const store = useStore()
const changeItems = () => {
  store.$patch(state => {
    state.items.push({ name: '王五', age: 30 })
    state.hasChanged = true
  })
}
</script>

<template>
<div v-for="(item, index) in store.items" :key="index">{{ item.name }} -- {{item.age}}</div>
<button @click="changeItems">changeItems</button>

</template>

8.5 更换state

  • 可以将store中的state整个进行替换
store.$state = { counter: 666, name: 'Paimon' }
  • 也可以替换应用程序的Pinia实例上的state
pinia.state.value = {}

9. getters

  • getters等同于Computed计算属性,getters可以接收state的值并进行计算处理

9.1 定义和获取getters

# 定义getters

import { defineStore } from 'pinia'

export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 1
  }),
  // 声明getters
  getters: {
    countDouble(state) {
      return state.count * 2
    }
  }
})
# 使用getters

<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count, countDouble } = storeToRefs(useStore())
</script>

<template>
<div>count: {{ count }}</div>
<div>countDouble: {{ countDouble }}</div>
</template>

9.2 访问同一个store的其他getters

# 定义并使用其他getters

import { defineStore } from 'pinia'

export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 1
  }),
  // 声明getters
  getters: {
    countDouble(state) {
      return state.count * 2
    },
    countDoublePlus() { // 使用其他getters
      return this.countDouble + 1
    }
  }
})
# 访问getters

<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count, countDouble, countDoublePlus } = storeToRefs(useStore())
</script>

<template>
<div>count: {{ count }}</div>
<div>countDouble: {{ countDouble }}</div>
<div>countDoublePlus: {{ countDoublePlus }}</div>
</template>

9.3 getters传参

  • getter可以通过返回函数的方式接受参数
# getters接受参数

import { defineStore } from 'pinia'

export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 1
  }),
  // 声明getters
  getters: {
    paramsCount(state) {
      return (param) => {
        return param + state.count
      }
    }
  }
})
# 访问并传入参数

<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { paramsCount } = storeToRefs(useStore())
</script>

<template>
<div>count: {{ paramsCount(10) }}</div>

</template>

9.4 访问其他store中的getters

# 定义一个store

import { defineStore } from 'pinia'

export const useMainStore = defineStore('main', {
  // 声明state
  state: () => ({
    num: 1
  }),
  // 声明getters
  getters: {
    numDouble(state) {
      return state.num * 6
    }
  }
})
# 在原来的store中使用另一个store的getters

import { defineStore } from 'pinia'
import { useMainStore } from './main'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 1
  }),
  // 声明getters
  getters: {
    getMain(state) {
      const mainStore = useMainStore()
      return state.count * 4 + mainStore.numDouble
    }
  }
})
# 访问getters

<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { getMain } = storeToRefs(useStore())
</script>

<template>
<div>count: {{ getMain}}</div>
</template>

10. Actions

  • actions是Pinia中定义处理逻辑函数的部分,可以获取到当前store整体

10.1 处理同步任务

# 定义state和actions

import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 10
  }),
  // 声明actions
  actions: {
    changeCount(num) { // 这里可以接收参数
      this.count += num
    }
  }
})
# 修改state的值

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

const { count } = storeToRefs(useStore())
const { changeCount } = useStore() // 这里actions的方法,不可以从storeToRefs中解构出来

</script>

<template>
<div>{{ count }}</div>
<button @click="changeCount(2)">changeCount</button>

</template>

10.1 处理异步任务

# 定义

import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
  // 声明state
  state: () => ({
    count: 10
  }),
  // 声明actions
  actions: {
    changeCount(num) {
      setTimeout(() => {
        this.count += num
      }, 2000)
    }
  }
})
# 修改

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

const { count } = storeToRefs(useStore())
const { changeCount } = useStore()

</script>

<template>
<div>{{ count }}</div>
<button @click="changeCount(2)">changeCount</button>

</template>
  • 使用async/await或Promise等方式
import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        return error
      }
    },
  }
})