Pinia 的基本概念
Pinia 是 Vue 3 中的一个轻量级状态管理库,旨在简化状态管理的复杂性。它提供了简洁的 API 和强大的功能,使得状态管理变得更加简单和直观。
核心特点:
- 简洁的 API:Pinia 的 API 设计非常简洁,易于上手。
- 自动持久化:Pinia 支持自动持久化状态到本地存储。
- 类型安全:Pinia 支持 TypeScript,确保类型安全。
- 插件系统:Pinia 提供了丰富的插件系统,方便扩展功能。
Pinia 的安装与配置
安装 Pinia 可以通过 npm 或 yarn 安装 Pinia:
npm install pinia
# 或者
yarn add pinia
创建 Pinia 应用实例
在项目中创建一个 store 文件夹,并在其中创建一个 index.js 文件:
// store/index.js
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;
在 Vue 应用中使用 Pinia 在 main.js 或 main.ts 文件中引入 Pinia 并注册到 Vue 应用中:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import pinia from './store';
const app = createApp(App);
app.use(pinia);
app.mount('#app');
Pinia 的核心功能
1. 定义 Store
Pinia 中的状态管理主要通过定义 Store 来实现。Store 是一个包含状态、Getters 和 Actions 的对象。
// store/todo.js
import { defineStore } from 'pinia';
export const useTodoStore = defineStore({
id: 'todo',
state: () => ({
todos: [
{ id: 1, text: 'Learn Pinia', done: false },
{ id: 2, text: 'Build a project', done: true }
]
}),
getters: {
completedTodos: (state) => state.todos.filter(todo => todo.done),
remainingTodos: (state) => state.todos.filter(todo => !todo.done)
},
actions: {
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
},
toggleTodo(id) {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.done = !todo.done;
}
},
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
}
});
2. 使用 Store
在 Vue 组件中使用 Store 非常简单,只需通过 useTodoStore 获取 Store 实例即可。
<template>
<div>
<h1>Todo List</h1>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" :checked="todo.done" @change="toggleTodo(todo.id)" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">Remove</button>
</li>
</ul>
<input v-model="newTodoText" placeholder="New todo" @keyup.enter="addTodo" />
<button @click="addTodo">Add Todo</button>
</div>
</template>
<script setup>
import { useTodoStore } from '@/store/todo';
import { ref } from 'vue';
const newTodoText = ref('');
const todoStore = useTodoStore();
const { todos, addTodo, toggleTodo, removeTodo } = todoStore;
</script>
<style scoped>
.done {
text-decoration: line-through;
}
</style>
Pinia 与 Vuex 的比较
相似之处:
- 状态管理:两者都提供了状态管理功能。
- 模块化:都可以将状态拆分成多个模块。
- Getters 和 Actions:都支持 Getters 和 Actions。
不同之处:
- API 设计:Pinia 的 API 更加简洁,易于上手。
- 类型安全:Pinia 支持 TypeScript 类型推断,更加安全。
- 自动持久化:Pinia 支持自动持久化状态到本地存储。
- 插件系统:Pinia 提供了丰富的插件系统,方便扩展功能。
Pinia 实战案例
构建一个复杂的待办事项应用
需求分析
- 用户可以添加待办事项。
- 用户可以删除待办事项。
- 用户可以标记待办事项为完成。
- 用户可以筛选待办事项(全部、已完成、未完成)。
组件设计
- TodoList:显示所有待办事项。
- TodoItem:显示单个待办事项。
- AddTodo:添加新的待办事项。
- FilterTodos:筛选待办事项。
实现代码
TodoList
<template>
<div>
<h1>Todo List</h1>
<ul>
<TodoItem v-for="todo in filteredTodos" :key="todo.id" :todo="todo" />
</ul>
<AddTodo @add-todo="addTodo" />
<FilterTodos @filter-todos="filterTodos" />
</div>
</template>
<script setup>
import { useTodoStore } from '@/store/todo';
import TodoItem from '@/components/TodoItem.vue';
import AddTodo from '@/components/AddTodo.vue';
import FilterTodos from '@/components/FilterTodos.vue';
const todoStore = useTodoStore();
const { todos, addTodo, toggleTodo, removeTodo } = todoStore;
const filter = ref('all');
const filteredTodos = computed(() => {
if (filter.value === 'all') {
return todos;
} else if (filter.value === 'completed') {
return todoStore.completedTodos;
} else if (filter.value === 'remaining') {
return todoStore.remainingTodos;
}
});
function filterTodos(value) {
filter.value = value;
}
</script>
TodoItem
<template>
<li>
<input type="checkbox" :checked="todo.done" @change="toggleTodo(todo.id)" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">Remove</button>
</li>
</template>
<script setup>
import { defineProps, onMounted } from 'vue';
import { useTodoStore } from '@/store/todo';
const props = defineProps(['todo']);
const todoStore = useTodoStore();
function toggleTodo(id) {
todoStore.toggleTodo(id);
}
function removeTodo(id) {
todoStore.removeTodo(id);
}
</script>
<style scoped>
.done {
text-decoration: line-through;
}
</style>
AddTodo
<template>
<div>
<input v-model="newTodoText" placeholder="New todo" @keyup.enter="addTodo" />
<button @click="addTodo">Add Todo</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useTodoStore } from '@/store/todo';
const newTodoText = ref('');
const todoStore = useTodoStore();
function addTodo() {
todoStore.addTodo(newTodoText.value);
newTodoText.value = '';
}
</script>
FilterTodos
<template>
<div>
<button @click="filterTodos('all')">All</button>
<button @click="filterTodos('completed')">Completed</button>
<button @click="filterTodos('remaining')">Remaining</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(['filter-todos']);
function filterTodos(value) {
emit('filter-todos', value);
}
</script>
Pinia 的高级功能
1. 动态模块 Pinia 支持动态创建和销毁模块,这在某些场景下非常有用。
// store/dynamicModule.js
import { defineStore } from 'pinia';
export const useDynamicStore = defineStore({
id: 'dynamic',
state: () => ({
count: 0
}),
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
increment() {
this.count++;
}
}
});
// 动态创建模块
function createDynamicModule(moduleId) {
const module = defineStore({
id: moduleId,
state: () => ({
count: 0
}),
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
increment() {
this.count++;
}
}
});
return module;
}
// 使用动态模块
const dynamicModule = createDynamicModule('dynamic1');
console.log(dynamicModule.doubleCount); // 0
dynamicModule.increment();
console.log(dynamicModule.doubleCount); // 2
2. 热更新 Pinia 支持热更新,可以在开发过程中轻松修改状态。
// store/hotUpdate.js
import { defineStore } from 'pinia';
export const useHotUpdateStore = defineStore({
id: 'hotUpdate',
state: () => ({
count: 0
}),
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
increment() {
this.count++;
}
}
});
// 修改状态
const hotUpdateStore = useHotUpdateStore();
hotUpdateStore.increment();
console.log(hotUpdateStore.doubleCount); // 2
Pinia 的插件系统
Pinia 提供了一个强大的插件系统,可以方便地扩展 Pinia 的功能。
1. 自定义插件 示例代码:
// plugins/persistedState.js
import { createPersistedState } from 'pinia-plugin-persistedstate';
export default createPersistedState({
key: 'myState',
storage: window.localStorage
});
// 在 Pinia 应用实例中使用插件
import { createPinia } from 'pinia';
import persistedState from './plugins/persistedState';
const pinia = createPinia();
pinia.use(persistedState);
export default pinia;
2. 日志插件
// plugins/logger.js
export default (store) => {
store.subscribe((mutation, state) => {
console.log('Mutation:', mutation);
console.log('New State:', state);
});
};
// 在 Pinia 应用实例中使用插件
import { createPinia } from 'pinia';
import logger from './plugins/logger';
const pinia = createPinia();
pinia.use(logger);
export default pinia;
Pinia 的持久化策略
Pinia 提供了多种持久化策略,可以方便地将状态保存到本地存储。
1. 自动持久化
// store/todo.js
import { defineStore } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
export const useTodoStore = defineStore({
id: 'todo',
state: () => ({
todos: [
{ id: 1, text: 'Learn Pinia', done: false },
{ id: 2, text: 'Build a project', done: true }
]
}),
getters: {
completedTodos: (state) => state.todos.filter(todo => todo.done),
remainingTodos: (state) => state.todos.filter(todo => !todo.done)
},
actions: {
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
},
toggleTodo(id) {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.done = !todo.done;
}
},
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
}
});
// 在 Pinia 应用实例中使用插件
import { createPinia } from 'pinia';
import persistedState from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(createPersistedState());
export default pinia;
Pinia 的性能优化
1. 惰性加载 Pinia 支持惰性加载 Store,只有当 Store 被首次访问时才会被初始化。
// store/lazy.js
import { defineStore } from 'pinia';
export const useLazyStore = defineStore({
id: 'lazy',
state: () => ({
count: 0
}),
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
increment() {
this.count++;
}
}
});
// 惰性加载 Store
const lazyStore = useLazyStore();
console.log(lazyStore.doubleCount); // 0
lazyStore.increment();
console.log(lazyStore.doubleCount); // 2
2. 异步操作 Pinia 支持异步操作,可以方便地处理异步数据。
// store/async.js
import { defineStore } from 'pinia';
export const useAsyncStore = defineStore({
id: 'async',
state: () => ({
data: null
}),
getters: {
hasData(state) {
return !!state.data;
}
},
actions: {
async fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
this.data = data;
} catch (error) {
console.error('Error fetching data:', error);
}
}
}
});
// 使用异步操作
const asyncStore = useAsyncStore();
asyncStore.fetchData();
console.log(asyncStore.hasData); // false
setTimeout(() => {
console.log(asyncStore.hasData); // true
}, 2000);
Pinia 在大型项目中的应用
1. 模块化设计 在大型项目中,Pinia 的模块化设计非常重要。每个 Store 只负责一项功能,通过组合多个 Store 来构建复杂功能。
// store/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore({
id: 'user',
state: () => ({
name: '',
email: ''
}),
getters: {
fullName(state) {
return `${state.name} (${state.email})`;
}
},
actions: {
login(name, email) {
this.name = name;
this.email = email;
},
logout() {
this.name = '';
this.email = '';
}
}
});
// store/todo.js
import { defineStore } from 'pinia';
export const useTodoStore = defineStore({
id: 'todo',
state: () => ({
todos: []
}),
getters: {
completedTodos(state) {
return state.todos.filter(todo => todo.done);
},
remainingTodos(state) {
return state.todos.filter(todo => !todo.done);
}
},
actions: {
addTodo(text) {
this.todos.push({ id: Date.now(), text, done: false });
},
toggleTodo(id) {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.done = !todo.done;
}
},
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
}
}
});
2. 状态管理 在大型项目中,状态管理尤为重要。Pinia 提供了灵活的状态管理机制,可以通过模块化的方式组织状态。
示例代码:
// store/auth.js
import { defineStore } from 'pinia';
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
token: localStorage.getItem('token') || '',
user: JSON.parse(localStorage.getItem('user')) || {}
}),
getters: {
isLoggedIn(state) {
return !!state.token;
}
},
actions: {
login(token, user) {
this.token = token;
this.user = user;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
},
logout() {
this.token = '';
this.user = {};
localStorage.removeItem('token');
localStorage.removeItem('user');
}
}
});
3. 异步操作 在大型项目中,异步操作非常常见。Pinia 提供了异步操作的支持,可以方便地处理异步数据。
示例代码:
// store/api.js
import { defineStore } from 'pinia';
export const useApiStore = defineStore({
id: 'api',
state: () => ({
data: null
}),
getters: {
hasData(state) {
return !!state.data;
}
},
actions: {
async fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
this.data = data;
} catch (error) {
console.error('Error fetching data:', error);
}
}
}
});