Vue3中使用Pinia详解

2023-10-14 09:05:21 浏览数 (2)

Pinia介绍

Pinia是一个专门为Vue.js设计的状态管理库,它提供了一种简单和直观的方式来管理应用程序的状态。在使用Pinia时,可以轻松地创建定义状态的存储,然后将其与Vue组件绑定,使它们能够使用该状态。和上一个博客提到的Vuex相比,Pinia 更加简单易用,体积更小,同时具有更好的 TypeScript 支持和插件系统。

在Vue.js的官网中,我们可以看到Pinia目前已经取代Vuex,成为Vue生态系统的一部分。

安装和配置Pinia

安装和配置Pinia非常简单,像其他Vue插件一样,Pinia需要通过yarn或npm进行安装并且与Vue应用程序进行绑定,可以使用以下命令进行安装:

代码语言:javascript复制
yarn add pinia
# 或者使用 npm
npm install pinia

在安装完Pinia包之后,需要在main.ts文件中导入createPinia函数并将Pinia插件与Vue应用程序绑定,如下所示:

代码语言:javascript复制
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);

const pinia = createPinia();
app.use(pinia);

app.mount('#app');

使用 createPinia() 函数创建并初始化Pinia插件实例,将其与Vue应用程序绑定使用app.use(pinia)。至此,我们就可以使用Pinia来管理Vue应用程序的状态了。

Pinia的核心

Store

Store是 Pinia 中管理状态的核心概念。它相当于一个 Vue 组件中的状态,但是 Store是一个独立的模块。

Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字,这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use… 是一个符合组合式函数风格的约定。

defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。

定义Store的示例代码:

代码语言:javascript复制
import { defineStore } from 'pinia'

// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
  // 其他配置...
})

State

State 是 store 中存储数据的地方。通过定义 State,可以在 store 的任何位置访问和修改数据。

在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。 定义State的示例代码如下:

代码语言:javascript复制
import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})

Getter

Getter 用来获取从 state 派生的数据,类似于 Vue 组件中的 computed 计算属性。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

代码语言:javascript复制
export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})

Action

Action 相当于组件中的 方法。它们可以通过 defineStore() 中的 actions 属性来定义;Action 是一种将异步操作封装在 store中的方式,它是一个可以被调用的函数,也可以接收参数并修改 store 中的状态。 Action应该始终是同步的,并返回一个 Promise 对象,以便在处理异步操作时能够很好地处理结果。

Pinia 中的 Action 由 defineStore 创建,可以通过在 actions 中定义它们来使用它们。例如,下面是一个 store 中的 Action 定义:

代码语言:javascript复制
import { defineStore } from 'pinia'

export const myStore = defineStore('myStore',{ 
  state: () => ({
    message: 'Hello',
  }),
  actions: {
    async fetchMessage() {
      const response = await fetch('http://127.0.0.1:5173/message')
      const data = await response.json()
      this.message = data.message
    },
  },
})

在上面的示例中,我们为 myStore 定义了一个 Action , fetchMessage() ,它会从后台 API 中获取数据,并更新 store 中的状态。然后,我们可以从组件或其他 Action 中调用该 Action :

代码语言:javascript复制
import { useStore } from 'pinia'

export default {
  setup() {
    const store = useStore('myStore')

    function handleClick() {
      store.fetchMessage()
    }

    return {
      handleClick,
    }
  },
}

在上面的代码中,我们在组件中使用 useStore 钩子来获取 store 实例,然后将其传递给 fetchMessage() 方法。该方法将从应用程序的后台获取数据,并更新存储器中的状态。最后,公开了一个 handleClick() 方法,以便组件可以调用它并触发 Action 。

创建和使用Pinia

创建Pinia

前面我们已经安装和配置好了Pinia,在创建Pinia之前,为了代码的统一管理和可维护性,我们依然先创建一个store文件夹,然后在来创建相关的Pinia,具体步骤如下

  1. 在src文件夹下新建store文件夹,后面所有涉及需要Pinia进行状态管理的代码都放在该文件夹下
  2. 在store文件夹下新建movieListStore.js文件,创建完成后,打开该文件
  3. 在movieListStore.js文件中引入Pinia中的defineStore 方法
代码语言:javascript复制
import { defineStore } from 'pinia'
  1. 创建defineStore 对象,定义一个useMovieListStore用于接收defineStore创建的对象,并将其通过export default 导出
代码语言:javascript复制
 const useMovieListStore = defineStore('movie',{ 
  state: () => ({
    isShow: true,
    movies: [],
  }),
  getters: {
    getIsShow() {
      return this.isShow
    },
    getMovies() {
      return this.movies
    },
  },
  actions: {
    setIsShow(value) {
      this.isShow = value
    },
    async fetchMovies() {
      const response = await fetch('https://api.movies.com/movies')
      const data = await response.json()
      this.movies = data
    },
  },
})
export default useMovieListStore 

在上面的代码中,我们使用action定义了两个方法,一个同步方法setIsShow, 一个异步方法 fetchMovies 注意: 这里需要注意,官方建议我们在定义钩子函数时,建议使用use开头Store结尾的命名方式来对上面创建的对象进行命名,如上面的useMovieListStore

使用Pinia

前面我们已经创建好了Pinia,接下来,我们就可以在组件中使用了。 在Vue组件中使用store,我们需要通过 useStore() 函数访问store的实例。 在Vue组件中使用Pinia的步骤如下

  1. 先使用 import 引入Pinia 中的 useStore
代码语言:javascript复制
import { useStore } from 'pinia'
  1. 创建useStore对象
代码语言:javascript复制
const store = useStore('movie')
  1. 在需要获取状态的地方通过上面定义的store.getIsShow()获取状态
代码语言:javascript复制
return {
   isShow: store.getIsShow(),
}

Menu.vue中完整的示例代码如下:

代码语言:javascript复制
<template>
  <nav>
    <ul>
      <li v-show="isShow">{{ $route.name }} </li>
      <li><router-link to="/">Home</router-link></li>
      <li><router-link to="/movies">Movies</router-link></li>
    </ul>
  </nav>
</template>

<script>
import { defineComponent } from 'vue'
import { useStore } from 'pinia'

export default defineComponent({
  name: 'Menu',

  setup() {
    const store = useStore('movie')

    return {
      isShow: store.getIsShow(),
    }
  },
})
</script>

Pinia的Option Store方式定义 Store

Option Store方式定义 Store 与 Vue 的选项式 API 类似,我们通过传入一个带有 state、actions 与 getters 属性的 Option 对象来定义,示例代码如下:

代码语言:javascript复制
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count  
    },
  },
})

我们可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

Pinia的Setup Store方式定义 Store

Setup Store与Option Store稍有不同,它与 Vue 组合式 API 的 setup 函数 相似,我们通过传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。示例代码如下:

代码语言:javascript复制
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value  
  }

  return { count, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

示例代码

下面通过一个实例来完整的说明Pinia状态管理的使用方法,现在要实现如下效果: 现在页面上需要完成两个功能,一个功能是通过监听isShow的值,来控制不同页面跳转时,底部菜单栏button的显示和隐藏;另一个功能是通过一个函数连接网络获取电影列表,并在详情页展示出来

在选项式API中,实现代码如下:

  1. main.js中的代码
代码语言:javascript复制
// main.js

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

const app = createApp(App)

app.use(createPinia())
app.use(movieStore)

app.mount('#app')
  1. store文件夹下movieStore.js中的代码
代码语言:javascript复制
// store/movieStore.js

import { defineStore } from 'pinia'

export const useMovieListStore = defineStore('movie',{ 
  state: () => ({
    isShow: true,
    movies: [],
  }),
  getters: {
    getIsShow() {
      return this.isShow
    },
    getMovies() {
      return this.movies
    },
  },
  actions: {
    setIsShow(value) {
      this.isShow = value
    },
    async fetchMovies() {
      const response = await fetch('https://api.movies.com/movies')
      const data = await response.json()
      this.movies = data
    },
  },
})
  1. components文件夹下Menu.vue文件的代码
代码语言:javascript复制
<!-- components/Menu.vue -->

<template>
  <nav>
    <ul>
      <li v-show="isShow">{{ $route.name }} </li>
      <li><router-link to="/">Home</router-link></li>
      <li><router-link to="/movies">Movies</router-link></li>
    </ul>
  </nav>
</template>

<script>
import { defineComponent } from 'vue'
import { useStore } from 'pinia'

export default defineComponent({
  name: 'Menu',

  setup() {
    const store = useStore('movie')

    return {
      isShow: store.getIsShow(),
    }
  },
})
</script>
  1. components文件夹下MovieList.vue的代码
代码语言:javascript复制
<!-- components/MovieList.vue -->

<template>
  <ul>
    <li v-for="movie in movies" :key="movie.id">
      <router-link :to="`/movies/${movie.id}`">{{ movie.title }}</router-link>
    </li>
  </ul>
</template>

<script>
import { defineComponent } from 'vue'
import { useStore } from 'pinia'

export default defineComponent({
  name: 'MovieList',

  setup() {
    const store = useStore('movie')

    store.fetchMovies()

    return {
      movies: store.getMovies(),
    }
  },
})
</script>
  1. views文件夹下MovieDetails.vue 的代码
代码语言:javascript复制
<!-- views/MovieDetails.vue -->

<template>
  <div v-if="movie">
    <h2>{{ movie.title }}</h2>
    <p>{{ movie.description }} </p>
  </div>
  <div v-else>
    <h2>Movie Not Found</h2>
  </div>
</template>

<script>
import { defineComponent } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'pinia'

export default defineComponent({
  name: 'MovieDetails',

  setup() {
    const route = useRoute()
    const store = useStore('movie')
    const movieId = route.params.id
    const movie = store.getMovies().find((movie) => movie.id === movieId)

    return {
      movie,
    }
  },
})
</script>

上面代码展示了如何使用 Pinia 中的 store、getter 和 action 共享和管理状态。其中,movieStore 定义了一个包含 isShow 和 movies 两个状态的 store,以及一个用于修改 isShow 和获取电影列表的 action。在 Menu.vue 组件中,我们使用 useStore 钩子从 store 中获取 isShow 状态,并根据其值控制底部菜单栏 button 的显示和隐藏。在 MovieList.vue 组件中,我们使用 useStore 钩子从 store 中获取 movies 状态,并使用 fetchMovies() action 来从网络获取电影列表。在 MovieDetails.vue 组件中,我们使用 useRoute 钩子获取当前页面的路由参数 id ,使用 useStore 钩子从 store 中获取 movies 状态,并根据 movieId 以及 getMovies() getter 得到当前电影的详细信息。

注意,在 setup() 钩子中,我们使用 useStore 钩子从 store 中获取状态和执行操作。由于 useStore 钩子返回的是一个响应式的代理,因此我们无需手动响应式地更新状态。而且,我们还可以将组件与 store 解耦,让它们更易于测试和重用。

在组合式API中,实现代码略有不同,这里只以MovieList.vue页面举例,其它页面写法类似,不在展示, MovieList.vue页面代码如下:

代码语言:javascript复制
<!-- components/MovieList.vue -->

<template>
  <ul>
    <li v-for="movie in movies" :key="movie.id">
      <router-link :to="`/movies/${movie.id}`">{{ movie.title }}</router-link>
    </li>
  </ul>
</template>

<script setup>
import { onMounted, computed } from 'vue'
import { useStore } from 'pinia'

const store = useStore('movie')

onMounted(() => {
  store.fetchMovies()
})

const movies = computed(() => store.getMovies())
</script>

OK,关于Vue3中使用Pinia做全局状态管理的使用就介绍到这里,喜欢的小伙伴点赞关注收藏哦!

0 人点赞