From 0f964e49f6b8c3db2c428ab78b71f29427d3ae12 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Mon, 13 May 2024 17:17:44 +0800 Subject: [PATCH 1/2] z --- .../20240513-pinia.md" | 371 ++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240513-pinia.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240513-pinia.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240513-pinia.md" new file mode 100644 index 0000000..59be493 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240513-pinia.md" @@ -0,0 +1,371 @@ +### 定义 Store + +在深入研究核心概念之前,我们得知道 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字: +``` +js +import { defineStore } from 'pinia' +``` +// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。 +// (比如 `useUserStore`,`useCartStore`,`useProductStore`) +// 第一个参数是你的应用中 Store 的唯一 ID。 +export const useAlertsStore = defineStore('alerts', { + // 其他配置... +}) +这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use... 是一个符合组合式函数风格的约定。 + +defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。 + +### Option Store +与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象 +``` +js +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)。 + +### Setup Store +也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。 +``` +js +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) +``` +在 Setup Store 中: + +ref() 就是 state 属性 +computed() 就是 getters +function() 就是 actions +注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。 + +Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。 + +Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样: +``` +ts +import { inject } from 'vue' +import { useRoute } from 'vue-router' + +export const useSearchFilters = defineStore('search-filters', () => { + const route = useRoute() + // 这里假定 `app.provide('appProvided', 'value')` 已经调用过 + const appProvided = inject('appProvided') + + // ... + + return { + // ... + } +}) +WARNING +``` +不要返回像 route 或 appProvided (上例中)之类的属性,因为它们不属于 store,而且你可以在组件中直接用 useRoute() 和 inject('appProvided') 访问。 + +你应该选用哪种语法? +和在 Vue 中如何选择组合式 API 与选项式 API 一样,选择你觉得最舒服的那一个就好。如果你还不确定,可以先试试 Option Store。 + +### 使用 Store +虽然我们前面定义了一个 store,但在我们使用 +``` +TIP + +如果你还不会使用 setup 组件,你也可以通过映射辅助函数来使用 Pinia。 + +你可以定义任意多的 store,但为了让使用 pinia 的益处最大化 (比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store。 + +一旦 store 被实例化,你可以直接访问在 store 的 state、getters 和 actions 中定义的任何属性。我们将在后续章节继续了解这些细节,目前自动补全将帮助你使用相关属性。 + +请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value。就像 setup 中的 props 一样,我们不能对它进行解构: +``` +vue + +``` +### 从 Store 解构 +为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上: +``` +vue + +``` +### State + +在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。 +``` +js +import { defineStore } from 'pinia' + +const useStore = defineStore('storeId', { + // 为了完整类型推理,推荐使用箭头函数 + state: () => { + return { + // 所有这些属性都将自动推断出它们的类型 + count: 0, + name: 'Eduardo', + isAdmin: true, + items: [], + hasChanged: true, + } + }, +}) +``` +TIP + +如果你使用的是 Vue 2,你在 state 中创建的数据与 Vue 实例中的 data 遵循同样的规则,即 state 对象必须是清晰的,当你想向其添加新属性时,你需要调用 Vue.set() 。参考:Vue#data。 + +### TypeScript +你并不需要做太多努力就能使你的 state 兼容 TS。确保启用了 strict,或者至少启用了 noImplicitThis,Pinia 将自动推断您的状态类型! 但是,在某些情况下,您应该帮助它进行一些转换: +``` +ts +const useStore = defineStore('storeId', { + state: () => { + return { + // 用于初始化空列表 + userList: [] as UserInfo[], + // 用于尚未加载的数据 + user: null as UserInfo | null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` +如果你愿意,你可以用一个接口定义 state,并添加 state() 的返回值的类型。 +``` +ts +interface State { + userList: UserInfo[] + user: UserInfo | null +} + +const useStore = defineStore('storeId', { + state: (): State => { + return { + userList: [], + user: null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` +### 访问 state +默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。 +``` +js +const store = useStore() +``` +store.count++ +### 重置 state +使用选项式 API 时,你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。 +``` +js +const store = useStore() +``` +store.$reset() +在 $reset() 内部,会调用 state() 函数来创建一个新的状态对象,并用它替换当前状态。 + +在 Setup Stores 中,您需要创建自己的 $reset() 方法: +``` +ts +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + + function $reset() { + count.value = 0 + } + + return { count, $reset } +}) +``` + +### 使用选项式 API 的用法 + +在下面的例子中,你可以假设相关 store 已经创建了: +``` +js +// 示例文件路径: +// ./src/stores/counter.js + +import { defineStore } from 'pinia' + +const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), +}) +``` +如果你不能使用组合式 API,但你可以使用 computed,methods,...,那你可以使用 mapState() 辅助函数将 state 属性映射为只读的计算属性: +``` +js +import { mapState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // 可以访问组件中的 this.count + // 与从 store.count 中读取的数据相同 + ...mapState(useCounterStore, ['count']) + // 与上述相同,但将其注册为 this.myOwnName + ...mapState(useCounterStore, { + myOwnName: 'count', + // 你也可以写一个函数来获得对 store 的访问权 + double: store => store.count * 2, + // 它可以访问 `this`,但它没有标注类型... + magicValue(store) { + return store.someGetter + this.count + this.double + }, + }), + }, +} +``` +### 可修改的 state + +如果你想修改这些 state 属性 (例如,如果你有一个表单),你可以使用 mapWritableState() 作为代替。但注意你不能像 mapState() 那样传递一个函数: +``` +js +import { mapWritableState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // 可以访问组件中的 this.count,并允许设置它。 + // this.count++ + // 与从 store.count 中读取的数据相同 + ...mapWritableState(useCounterStore, ['count']) + // 与上述相同,但将其注册为 this.myOwnName + ...mapWritableState(useCounterStore, { + myOwnName: 'count', + }), + }, +} +``` +TIP + +对于像数组这样的集合,你并不一定需要使用 mapWritableState(),mapState() 也允许你调用集合上的方法,除非你想用 cartItems = [] 替换整个数组。 + +### 变更 state + +除了用 store.count++ 直接改变 store,你还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性: +``` +js +store.$patch({ + count: store.count + 1, + age: 120, + name: 'DIO', +}) +``` +不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要你创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。 +``` +js +store.$patch((state) => { + state.items.push({ name: 'shoes', quantity: 1 }) + state.hasChanged = true +}) +``` + +两种变更 store 方法的主要区别是,$patch() 允许你将多个变更归入 devtools 的同一个条目中。同时请注意,直接修改 state,$patch() 也会出现在 devtools 中,而且可以进行 time travel (在 Vue 3 中还没有)。 + +### 替换 state +你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它。 +``` +js +// 这实际上并没有替换`$state` +store.$state = { count: 24 } +// 在它内部调用 `$patch()`: +store.$patch({ count: 24 }) +``` +你也可以通过变更 pinia 实例的 state 来设置整个应用的初始 state。这常用于 SSR 中的激活过程。 +``` +js +pinia.state.value = {} +``` +### 订阅 state +类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。 +``` +js +cartStore.$subscribe((mutation, state) => { + // import { MutationType } from 'pinia' + mutation.type // 'direct' | 'patch object' | 'patch function' + // 和 cartStore.$id 一样 + mutation.storeId // 'cart' + // 只有 mutation.type === 'patch object'的情况下才可用 + mutation.payload // 传递给 cartStore.$patch() 的补丁对象。 + + // 每当状态发生变化时,将整个 state 持久化到本地存储。 + localStorage.setItem('cart', JSON.stringify(state)) +}) +``` +默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离: +``` +vue + +``` +TIP + +你可以在 pinia 实例上使用 watch() 函数侦听整个 state。 +``` +js +watch( + pinia.state, + (state) => { + // 每当状态发生变化时,将整个 state 持久化到本地存储。 + localStorage.setItem('piniaState', JSON.stringify(state)) + }, + { deep: true } +) +``` \ No newline at end of file -- Gitee From 620f63ca6c357c3f7302b92ad05835a9b44fc0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=A0=87=E6=98=9F=E5=AE=89?= <11802724+zhang-biao-xingan@user.noreply.gitee.com> Date: Sun, 19 May 2024 14:18:30 +0000 Subject: [PATCH 2/2] =?UTF-8?q?add=20=E5=BC=A0=E6=A0=87=E6=98=9F=E5=AE=89/?= =?UTF-8?q?20240516-=E8=B7=AF=E7=94=B1=E5=BA=94=E7=94=A8.md.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 张标星安 <11802724+zhang-biao-xingan@user.noreply.gitee.com> --- ...57\347\224\261\345\272\224\347\224\250.md" | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240516-\350\267\257\347\224\261\345\272\224\347\224\250.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240516-\350\267\257\347\224\261\345\272\224\347\224\250.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240516-\350\267\257\347\224\261\345\272\224\347\224\250.md" new file mode 100644 index 0000000..d093f90 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240516-\350\267\257\347\224\261\345\272\224\347\224\250.md" @@ -0,0 +1,29 @@ +### 通过路由方式实现增删改查 +#### 思路: +1. 配置路由: +~~~js +// src/router/router.js + import { createRouter, createWebHistory } from 'vue-router'; + import Home from './components/Home.vue'; + import ManageItem from './components/Edit.vue'; + const router = createRouter({ + history: createWebHistory(), + routes:[ + {path:'/home',component:Home}, + { path: '/edit/:id?', + name: 'Edit', + component: Edit, + }, + {path:'/',component:App}, + // 这里也可以加上redirect:'/home' 表示一打开就跳转到列表页,但是不推荐 + ] + } ); + export default router; +~~~ +2. 配置入口文件: +~~~js + import router from '.Router/router'; + const app = createApp(App); + app.use(router); +~~~ +\ No newline at end of file \ No newline at end of file -- Gitee