5-pinia的基本使用和核心实现

54 阅读1分钟

pinia的基本使用实现官网的todoList

main.js

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')

store/index.js or store/todoList.js

// => todoList.js
import { defineStore } from "pinia";

export default defineStore("todoList", {
  state: () => ({
    todos: [],
    filter: "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: {
    finishTodos(state) {
      return state.todos.filter((todo) => todo.isFinished);
    },
    unFinishTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished);
    },
    filterTodos(state) {
      switch (this.filter) {
        case "all":
          return this.todos;
        case "finished":
          return this.finishTodos;
        case "unFinished":
          return this.unFinishTodos;
      }
    },
  },
});
//=> index.js
import useTodoListStore from "./todoList";

export { useTodoListStore };

组件书写,目录如下

image.png

// =>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>

<style scoped></style>
// => 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>
.active {
    text-decoration: none;
    color: black;
}
a {
    margin-right: 15px;
}
</style>
//=> TodoForm.vue
<template>
  <div>
    <input type="text" placeholder="输入点内容" v-model.trim="inputRef" />
    <button @click="addTodo">增加</button>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useTodoListStore } from "../../store";
const todoListStore = useTodoListStore();

const inputRef = ref("");

const addTodo = () => {
  if(!inputRef.value) return;
  todoListStore.addTodo(inputRef.value);
  inputRef.value = "";
};
</script>
// => Todos.vue
<template>
  <div>
    <div v-for="item of todoListStore.filterTodos">
      <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>

App.vue

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

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

效果图如下:

image.png

image.png

image.png

image.png

pinia的核心实现

目录如下

image.png

  • 实现 createPinia
  • 实现 defineStore
  • api中的patch方法

createPinia.js

import { reactive } from "vue";
import { patch } from "./api";

// => 创建pinia
export default () => {
  // 1.数据变成响应式的
  const piniaStore = reactive({});

  // 2.能被use的基础
  function install(app) {
    // 3.用渗透的方式传递数据
    app.provide("setSubStore", setSubStore);
  }

  function setSubStore(name, store) {
    if (!piniaStore[name]) {
      // =>相当于 {} => obj[name] = {xxx}
      piniaStore[name] = store;
      piniaStore[name].$patch = patch;
    }

    return piniaStore;
  }

  return {
    install,
  };
};

api.js

// => value 是一个对象 如果在这个里面的话修改值
export function patch(value) {
    const store = this;

    for(let key in value) {
        store[key] = value[key];
    }
}

defineStore.js

import { computed, inject, reactive, toRef, ref } from "vue";

export default (name, {
    state,
    getters,
    actions
}) => {
    const store = {};

    // => 处理 store
    if(state && typeof state === 'function') {
        const _state = state();
        
        store.$state = reactive(_state);
        // console.log(store.$state)
        // => 把state上的属性放到 store上
        for(let key in _state) {
            store[key] = toRef(store.$state, key);
            // store[key] = ref(store.$state[key]);
        }
    }

    // => 处理 actions
    if(actions && Object.keys(actions).length > 0) {
        for(let method in actions) {
            store[method] = actions[method];
        }
    }

    // => 处理getters 使用computed进行处理
    if(getters && Object.keys(getters).length > 0) {
        for(let getter in getters) {
            store[getter] = computed(getters[getter].bind(store.$state, store.$state));
            store.$state[getter] = store[getter];
        }
    }

    // 返回一个函数
    return () => {
        // =>获取到 createPinia 渗透下来的方法进行注册 模块
        const setSubStore = inject("setSubStore");
        const piniaStore = setSubStore(name, reactive(store));

        return piniaStore[name];
    }
}

index.js

import createPinia from "./createPinia";
import defineStore from "./defineStore";

export { createPinia, defineStore };
  • 实现的效果如上面的结果一直
  • 构造出来的格式如下:

image.png