vue知识点之pinia使用
Pinia是一个专门为Vue.js设计的状态管理库,它提供了一种简单和直观的方式来管理应用程序的状态。在使用Pinia时,可以轻松地创建定义状态的存储,然后将其与Vue组件绑定,使它们能够使用该状态。
在Vue.js的官网中,我们可以看到Pinia目前已经取代Vuex,成为Vue生态系统的一部分。
一、安装和配置Pinia
⏰ 安装
yarn add pinia
# 或者使用 npm
npm install pinia
⏰ 绑定
import { createApp } from 'vue'; import { createPinia } from 'pinia'; import App from './App.vue'; const app = createApp(App); app.use(createPinia()); app.mount('#app');
使用 createPinia() 函数创建并初始化Pinia插件实例,将其与Vue应用程序绑定使用app.use(pinia)。至此,我们就可以使用Pinia来管理Vue应用程序的状态了。
二、Pinia的核心
Store是 Pinia 中管理状态的核心概念。它相当于一个 Vue 组件中的状态,但是 Store是一个独立的模块。
defineStore()
Store 是用 defineStore() 定义的,它接收两个参数
⏰ 第一个参数:“字符串”
defineStore() 的第一个参数要求是一个独一无二的名字,这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。
🔊:为了养成习惯性的用法,将返回的函数命名为 use… 是一个符合组合式函数风格的约定。
⏰ 第二个参数:“函数” 或者 “对象”
defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。
🌰示例代码:
import { defineStore } from 'pinia' // 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`) // 第一个参数是你的应用中 Store 的唯一 ID。 export const useAlertsStore = defineStore('alerts', { // 其他配置... })
State
🔊 State 相当于组件里面的data, 是 store 中存储数据的地方。通过定义 State,可以在 store 的任何位置访问和修改数据。
在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。 定义State的示例代码如下:
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 作为第一个参数:
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 定义:
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 :
import { useStore } from 'pinia' export default { setup() { const store = useStore('myStore') function handleClick() { store.fetchMessage() } return { handleClick, } }, }
在上面的代码中,我们在组件中使用 useStore 钩子来获取 store 实例,然后将其传递给 fetchMessage() 方法。该方法将从应用程序的后台获取数据,并更新存储器中的状态。最后,公开了一个 handleClick() 方法,以便组件可以调用它并触发 Action 。
三、Pinia的创建和使用
1. 创建Pinia
前面我们已经安装和配置好了Pinia,在创建Pinia之前,为了代码的统一管理和可维护性,我们依然先创建一个store文件夹,然后在来创建相关的Pinia。
具体步骤如下:
1. 在src文件夹下新建store文件夹,后面所有涉及需要Pinia进行状态管理的代码都放在该文件夹下
2. 在store文件夹下新建movieListStore.js文件,创建完成后,打开该文件
3. 在movieListStore.js文件中引入Pinia中的defineStore 方法
🌰 创建defineStore 对象,定义一个useMovieListStore用于接收defineStore创建的对象,并将其通过export default 导出
import { defineStore } from 'pinia' 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
2. 使用Pinia
前面我们已经创建好了Pinia,接下来,我们就可以在组件中使用了。 在Vue组件中使用store,我们需要通过 useStore() 函数访问store的实例。 在Vue组件中使用Pinia的步骤如下
⏰ 1. 先使用 import 引入movieListStore 中的 useMovieListStore
import { useMovieListStore } from "@/store/useMovieListStore.js";
⏰ 2. 创建movieListStore对象
const store = movieListStore()
⏰ 3. 在需要获取状态的地方通过上面定义的store.getIsShow()获取状态
return { isShow: store.getIsShow(), }
🌰 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 { useMovieListStore } from "@/store/useMovieListStore.js"; export default defineComponent({ name: 'Menu', setup() { const store = useMovieListStore() return { isShow: store.getIsShow(), } }, }) </script>
四、Pinia创建的两种方式
1. 对象式
Option Store方式定义 Store 与 Vue 的选项式 API 类似,我们通过传入一个带有 state、actions 与 getters 属性的 Option 对象来定义。
🌰示例代码如下:
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)。
2. 函数式
Setup Store与Option Store稍有不同,它与 Vue 组合式 API 的 setup 函数 相似,我们通过传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
🌰示例代码如下:
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中的代码
// 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')
⏰ 2. 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 }, }, })
⏰ 3. 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 { useMovieListStore } from "@/store/movieStore.js"; export default defineComponent({ name: "Menu", setup() { const store = useMovieListStore(); return { isShow: store.getIsShow() }; }, }); </script>
⏰ 4. 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 { useMovieListStore } from "@/store/movieStore.js"; export default defineComponent({ name: "MovieList", setup() { const store = useMovieListStore(); store.fetchMovies(); return { movies: store.getMovies() }; }, }); </script>
⏰ 5. 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 { useMovieListStore } from "@/store/movieStore.js"; export default defineComponent({ name: "MovieDetails", setup() { const route = useRoute(); const store = useMovieListStore(); 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页面代码如下:
<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>