本系列文章是本人研究 Pinia 的相关记录。
Pinia 是近年比较热门的 Vue 相关状态管理库,我对其的学习步骤如下:
- 使用 pinia.js 实现一个简单的 TodoList 功能;
- 观察 pinia.js 的 api 和数据结构,自己实现一个伪 pinia,并将 TodoList 的依赖转为伪 pinia;
- 对比 pinia.js 源码和自己的实现。
(一)使用 pinia.js 实现一个简单的 TodoList 功能
首先在 main.js 中引入:
1 2 3 4
| import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
|
在 src/store 目录下新建 todoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import { defineStore } from 'pinia'
export default defineStore('todoList', { state: () => ({ todos: [], filter: 'all', nextId: 0 }), actions: { addTodo(text) { this.todos.push({ id: this.nextId++, text, isFinished: false }) }, toggleTodo(id) { this.todos = this.todos.map((todo) => { if (todo.id === id) { todo.isFinished = !todo.isFinished }
return todo }) }, removeTodo(id) { this.todos = this.todos.filter((todo) => todo.id !== id) } }, getters: { finishedTodos(state) { return state.todos.filter((todo) => todo.isFinished) }, unFinishedTodos(state) { return state.todos.filter((todo) => !todo.isFinished) }, filteredTodos(state) { switch (this.filter) { case 'finished': return this.finishedTodos case 'unfinished': return this.unFinishedTodos default: return this.todos } } } })
|
在 src/store 目录下新建 index.js
1 2 3
| import useTodoListStore from './todoList'
export { useTodoListStore }
|
如此,就能在 src/App.vue 中引入:
1 2 3 4 5 6
| import { useTodoListStore } from './store'
const todoListStore = useTodoListStore()
console.log(todoListStore)
|
将 todoListStore
打印出来观察:

- 这是一个
Proxy
对象;
- 有
$dispose
、$id
、$onAction
、$patch
、$reset
、$subscribe
等一些由 pinia 定义的属性以及一些继承的属性;
- 在
state
中定义的 filter
、nextId
、todos
都是 ObjectRefImpl
类型的对象,都成为了 todoListStore
的属性;
- 在
actions
中定义的 addTodo
、removeTodo
、toggleTodo
等方法都成为了 todoListStore
的属性;
- 在
getters
中定义的 filteredTodos
、finishedTodos
、unfinishedTodos
都是 ComputedRefImpl
类型的对象,都成为了 todoListStore
的属性。
观察完后将 src/App.vue 的代码改成:
1 2 3 4 5 6 7
| <template> <todo-list></todo-list> </template>
<script setup> import TodoList from '@/components/TodoList/index.vue' </script>
|
下面来具体写 TodoList 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div> <todo-tab></todo-tab> <todo-form></todo-form> <todos></todos> </div> </template>
<script setup> import TodoTab from './TodoTab.vue' import TodoForm from './TodoForm.vue' import Todos from './Todos.vue' </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> <a href="javascript:;" :class="{ active: todoListStore.filter === 'all' }" @click="setFilter('all')" >All</a > <a href="javascript:;" :class="{ active: todoListStore.filter === 'finished' }" @click="setFilter('finished')" >Finished</a > <a href="javascript:;" :class="{ active: todoListStore.filter === 'unfinished' }" @click="setFilter('unfinished')" >Unfinished</a > </div> </template>
<script setup> import { useTodoListStore } from '@/store' const todoListStore = useTodoListStore()
const setFilter = (filter) => { todoListStore.$patch({ filter }) } </script>
<style scoped> a { margin-right: 15px; } .active { text-decoration: none; color: #000; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <template> <div> <input placeholder="Input something..." type="text" v-model="inputRef" /> <button @click="addTodo">Add Todo</button> </div> </template>
<script setup> import { ref } from 'vue' import { useTodoListStore } from '@/store'
const todoListStore = useTodoListStore() const inputRef = ref('')
const addTodo = () => { todoListStore.addTodo(inputRef.value) inputRef.value = '' } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div> <div v-for="item of todoListStore.filteredTodos" :key="item.id"> <input type="checkbox" :checked="item.isFinished" @click="todoListStore.toggleTodo(item.id)" /> <span :class="{ finished: item.isFinished }" >{{item.text}}</span > <button @click="todoListStore.removeTodo(item.id)"> Delete </button> </div> </div> </template>
<script setup> import { useTodoListStore } from '@/store'
const todoListStore = useTodoListStore() </script>
<style scoped> .finished { text-decoration: line-through; } </style>
|
本小节完,下一小节开始自己实现 pinia 相关的功能。