JackAtlas

Command Palette

Search for a command to run...

Pinia 学习笔记(一)
over 2 years ago
前端开发

Pinia 学习笔记(一)

Pinia 是近年比较热门的 Vue 相关状态管理库,我对其的学习步骤如下:

  1. 使用 pinia.js 实现一个简单的 TodoList 功能;
  2. 观察 pinia.js 的 api 和数据结构,自己实现一个伪 pinia,并将 TodoList 的依赖转为伪 pinia。

(一)使用 pinia.js 实现一个简单的 TodoList 功能

首先在 main.js 中引入:

// src/main.js
import { createPinia } from 'pinia'

createApp(App).use(createPinia()).mount('#app')

在 src/store 目录下新建 todoList.js

// src/store/todoList.js
import { defineStore } from 'pinia'

export default defineStore('todoList', {
  state: () => ({
    todos: [], // id: number, text: string, isFinished: boolean
    filter: 'all', // 'all' 'finished' 'unfinished'
    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

import useTodoListStore from './todoList'

export { useTodoListStore }

如此,就能在 src/App.vue 中引入:

// src/App.vue
import { useTodoListStore } from './store'

const todoListStore = useTodoListStore()

console.log(todoListStore)

将 todoListStore 打印出来观察:

todoListStore

  1. 这是一个 Proxy 对象;
  2. $dispose$id$onAction$patch$reset$subscribe 等一些由 pinia 定义的属性以及一些继承的属性;
  3. state 中定义的 filternextIdtodos 都是 ObjectRefImpl 类型的对象,都成为了 todoListStore 的属性;
  4. actions 中定义的 addTodoremoveTodotoggleTodo 等方法都成为了 todoListStore 的属性;
  5. getters 中定义的 filteredTodosfinishedTodosunfinishedTodos 都是 ComputedRefImpl 类型的对象,都成为了 todoListStore 的属性。

观察完后将 src/App.vue 的代码改成:

<template>
  <todo-list></todo-list>
</template>

<script setup>
  import TodoList from '@/components/TodoList/index.vue'
</script>

下面来具体写 TodoList 组件:

<!-- src/components/TodoList/index.vue -->
<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>
<!-- src/components/TodoList/TodoTab.vue -->
<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>
<!-- src/components/TodoList/TodoForm.vue -->
<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>
<!-- src/components/TodoList/Todos.vue -->
<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 相关的功能。