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>

posted on 2024-07-25 19:24  梁飞宇  阅读(1214)  评论(0)    收藏  举报