|
@@ -0,0 +1,653 @@
|
|
|
+## Vue2 与 Vue3 的区别
|
|
|
+
|
|
|
+`Vue3`由于完全由`TS`进行重写,在应用中对类型判断的定义和使用有很强的表现。同一对象的多个键返回值必须通过定义对应的接口(`interface`)来进行类型定义。要不然在 ESLint 时都会报错。
|
|
|
+
|
|
|
+`vue2` 的双向数据绑定是利用 `ES5` 的一个 `API Object.definePropert()`对数据进行劫持 结合 `发布订阅`模式的方式来实现的。`Vue3` 中使用了 `es6` 的 `ProxyAPI` 对数据代理。
|
|
|
+
|
|
|
+`Vue3`支持碎片(`Fragments`)
|
|
|
+
|
|
|
+Vue2 与 Vue3 最大的区别: Vue2 使用`Options API`而 Vue3 使用的`Composition API`
|
|
|
+Composition API 中,所有的逻辑可以集中在一个名为 setup() 的函数内定义,并根据功能进行分组和模块化。
|
|
|
+
|
|
|
+## 声明响应式状态的方式
|
|
|
+ref()
|
|
|
+reactive()
|
|
|
+shallowRef()
|
|
|
+shallowReactive()
|
|
|
+<script>
|
|
|
+import { ref, reactive, shallowRef, shallowReactive, onMounted } from 'vue';
|
|
|
+
|
|
|
+export default {
|
|
|
+ setup() {
|
|
|
+ const message = ref('Hello, Vue 3!');
|
|
|
+ const count = ref(0)
|
|
|
+ const obj = ref({
|
|
|
+ foo: 'foo',
|
|
|
+ bar: 'bar',
|
|
|
+ })
|
|
|
+ const array = ref([1, 2, 3])
|
|
|
+ return {
|
|
|
+ message,
|
|
|
+ count,
|
|
|
+ obj,
|
|
|
+ array
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
|
|
|
+// Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
|
|
|
+
|
|
|
+const obj = ref({
|
|
|
+ nested: { count: 0 },
|
|
|
+ arr: ['foo', 'bar']
|
|
|
+})
|
|
|
+
|
|
|
+function mutateDeeply() {
|
|
|
+ // 以下都会按照期望工作
|
|
|
+ obj.value.nested.count++
|
|
|
+ obj.value.arr.push('baz')
|
|
|
+}
|
|
|
+
|
|
|
+// 和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
|
|
|
+// shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。
|
|
|
+const state = shallowRef({ count: 1 })
|
|
|
+
|
|
|
+// 不会触发更改
|
|
|
+state.value.count = 2
|
|
|
+
|
|
|
+// 会触发更改
|
|
|
+state.value = { count: 2 }
|
|
|
+
|
|
|
+// shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新:
|
|
|
+
|
|
|
+
|
|
|
+const shallowArray = shallowRef([
|
|
|
+ /* 巨大的列表,里面包含深层的对象 */
|
|
|
+])
|
|
|
+
|
|
|
+// 这不会触发更新...
|
|
|
+shallowArray.value.push(newObject)
|
|
|
+// 这才会触发更新
|
|
|
+shallowArray.value = [...shallowArray.value, newObject]
|
|
|
+
|
|
|
+// 这不会触发更新...
|
|
|
+shallowArray.value[0].foo = 1
|
|
|
+// 这才会触发更新
|
|
|
+shallowArray.value = [
|
|
|
+ {
|
|
|
+ ...shallowArray.value[0],
|
|
|
+ foo: 1
|
|
|
+ },
|
|
|
+ ...shallowArray.value.slice(1)
|
|
|
+]
|
|
|
+
|
|
|
+```
|
|
|
+ reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新
|
|
|
+主要用于创建整个对象(包括嵌套对象和数组)的响应式状态,它可以递归地转换对象的所有属性。
|
|
|
+
|
|
|
+返回值:reactive 函数返回一个代理对象,这个对象上的所有属性都是响应式的,可以直接读取和修改,无需.value。
|
|
|
+```
|
|
|
+const state = reactive(
|
|
|
+ count: 0,
|
|
|
+ nested: {
|
|
|
+ bar: 2
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+<button @click="state.count++">
|
|
|
+ {{ state.count }}
|
|
|
+</button>
|
|
|
+
|
|
|
+// shallowReactive() 与 reactive() 类似,但只将对象本身转为响应式,而不将嵌套的属性转为响应式。
|
|
|
+const state = shallowReactive({
|
|
|
+ count: 0,
|
|
|
+ nested: {
|
|
|
+ bar: 2
|
|
|
+ }
|
|
|
+})
|
|
|
+// 更改状态自身的属性是响应式的
|
|
|
+state.count++
|
|
|
+
|
|
|
+// ...但下层嵌套对象不会被转为响应式
|
|
|
+isReactive(state.nested) // false
|
|
|
+
|
|
|
+// 不是响应式的
|
|
|
+state.nested.bar++
|
|
|
+</script>
|
|
|
+
|
|
|
+
|
|
|
+## 生命周期钩子变化
|
|
|
+
|
|
|
+```js
|
|
|
+Vue2 ~~~~~~~~~~~ vue3
|
|
|
+beforeCreate -> setup()
|
|
|
+created -> setup() // 实例没有创建,不能访问上下文
|
|
|
+beforeMount -> onBeforeMount
|
|
|
+mounted -> onMounted
|
|
|
+beforeUpdate -> onBeforeUpdate
|
|
|
+updated -> onUpdated
|
|
|
+beforeDestroy -> onBeforeUnmount
|
|
|
+destroyed -> onUnmounted
|
|
|
+activated -> onActivated
|
|
|
+deactivated -> onDeactivated
|
|
|
+```
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div>{{ message }}</div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { ref, onMounted } from 'vue';
|
|
|
+
|
|
|
+export default {
|
|
|
+ setup() {
|
|
|
+ const message = ref('Hello, Vue 3!');
|
|
|
+
|
|
|
+ function beforeCreateEvent() {
|
|
|
+ console.log('Before create event triggered');
|
|
|
+ }
|
|
|
+
|
|
|
+ function createdEvent() {
|
|
|
+ console.log('Created event triggered');
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ beforeCreateEvent();
|
|
|
+ createdEvent();
|
|
|
+ console.log(message, 'message')
|
|
|
+ });
|
|
|
+
|
|
|
+ return { message };
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+## 性能提升
|
|
|
+```
|
|
|
+vue3 => {
|
|
|
+ 编译阶段
|
|
|
+ 源码体积
|
|
|
+ 响应式系统
|
|
|
+}
|
|
|
+
|
|
|
+• diff 算法优化
|
|
|
+• 静态提升
|
|
|
+• 事件监听缓存
|
|
|
+• SSR 优化
|
|
|
+```
|
|
|
+<template>
|
|
|
+ <div id="content">
|
|
|
+ <p class="text">静态文本</p>
|
|
|
+ <p class="text">静态文本</p>
|
|
|
+ <p class="text">{{ message }}</p>
|
|
|
+ <p class="text">静态文本</p>
|
|
|
+ ...
|
|
|
+ <p class="text">静态文本</p>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+### 1.diff 算法
|
|
|
+```
|
|
|
+vue3 在 diff 算法中相比 vue2 增加了静态标记
|
|
|
+关于这个静态标记,其作用是为了会发生变化的地方添加一个 flag 标记,下次发
|
|
|
+生变化的时候直接找该地方进行比较
|
|
|
+```
|
|
|
+### 2.静态提升
|
|
|
+```
|
|
|
+Vue3 中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复
|
|
|
+用
|
|
|
+这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操
|
|
|
+作,优化了运行时候的内存占用
|
|
|
+```
|
|
|
+<span>你好</span>
|
|
|
+<div>{{ message }}</div>
|
|
|
+<script>
|
|
|
+// 没有做静态提升之前
|
|
|
+export function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return (_openBlock(), _createBlock(_Fragment, null, [
|
|
|
+ _createVNode("span", null, "你好"),
|
|
|
+ _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT
|
|
|
+ */)
|
|
|
+ ], 64 /* STABLE_FRAGMENT */))
|
|
|
+}
|
|
|
+// 做了静态提升之后
|
|
|
+const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /
|
|
|
+* HOISTED */)
|
|
|
+// _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff
|
|
|
+export function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return (_openBlock(), _createBlock(_Fragment, null, [
|
|
|
+ _hoisted_1,
|
|
|
+ _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT
|
|
|
+ */)
|
|
|
+ ], 64 /* STABLE_FRAGMENT */))
|
|
|
+}
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+### 事件监听缓存
|
|
|
+```
|
|
|
+未开启事件监听器缓存:
|
|
|
+```
|
|
|
+<script>
|
|
|
+export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return (_openBlock(), _createBlock("div", null, [
|
|
|
+ _createVNode("button", { onClick: _ctx.onClick }, "点我", 8 /* PROPS */, ["onClick"])
|
|
|
+ ]))
|
|
|
+})
|
|
|
+</script>
|
|
|
+```
|
|
|
+onClick属性直接绑定到组件实例的_ctx.onClick方法。每次渲染时,都会创建一个新的事件处理器引用。
|
|
|
+
|
|
|
+开启事件监听器缓存:
|
|
|
+```
|
|
|
+<script>
|
|
|
+export function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return (_openBlock(), _createBlock("div", null, [
|
|
|
+ _createVNode("button", {
|
|
|
+ onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
|
|
|
+ }, "点我")
|
|
|
+ ]))
|
|
|
+}
|
|
|
+</script>
|
|
|
+```
|
|
|
+在开启了事件监听器缓存的情况下,Vue.js会尝试复用已创建的事件处理器以提高性能。这里使用了闭包来缓存事件处理函数,_cache[1] 是一个闭包缓存变量,如果之前已经为 onClick 创建过处理函数,则从缓存 _cache 中获取,否则新建一个处理函数并赋值给 _cache[1]。这样,在后续的渲染过程中,如果_ctx.onClick方法没有变化,就会复用同一个事件处理器,从而避免不必要的内存分配和DOM操作。
|
|
|
+
|
|
|
+总结来说,开启事件监听器缓存是为了优化性能,减少无谓的DOM更新以及内存消耗。
|
|
|
+
|
|
|
+```
|
|
|
+### SSR 优化
|
|
|
+```
|
|
|
+当静态内容大到一定量级时候,会用 createStaticVNode 方法在客户端去生成一
|
|
|
+个 static node,这些静态 node,会被直接 innerHtml,就不需要创建对象,然后根
|
|
|
+据对象渲染
|
|
|
+```
|
|
|
+<div>
|
|
|
+ <div>
|
|
|
+ <span>你好</span>
|
|
|
+ </div>
|
|
|
+ ... // 很多个静态属性
|
|
|
+ <div>
|
|
|
+ <span>{{ message }}</span>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<script>
|
|
|
+ // 编译后
|
|
|
+ // 在Vue中,我们可以使用mergeProps方法来将父组件传递给子组件的props与子组件自定义的props进行合并,以生成最终的props对象
|
|
|
+ // ssrRenderAttrs: 这个方法主要用于服务器端渲染时处理组件的属性(attributes)。在Vue SSR过程中,当需要将组件渲染为HTML字符串时,此方法会被用来序列化并输出组件的attrs(即props、事件监听器等非DOM属性)。
|
|
|
+ // ssrInterpolate: 这个方法则可能用于在服务器端渲染模板字符串时进行插值表达式的解析和替换。在Vue SSR中,模板中的动态内容(如{{ expression }})需要在服务器端被正确地计算并插入到最终生成的HTML字符串中。
|
|
|
+ // _attrs: 这通常是组件实例的原始属性对象,包含了从父组件传递给子组件的props以及其他原生HTML属性。
|
|
|
+import { mergeProps as _mergeProps } from "vue"
|
|
|
+import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInter
|
|
|
+polate } from "@vue/server-renderer"
|
|
|
+export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup,
|
|
|
+$data, $options) {
|
|
|
+ const _cssVars = { style: { color: _ctx.color }}
|
|
|
+ _push(`<div${
|
|
|
+ _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
|
|
|
+ }><div><span>你好</span>...<div><span>你好</span><div><span>${
|
|
|
+ _ssrInterpolate(_ctx.message)
|
|
|
+ }</span></div></div>`)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+### 源码体积
|
|
|
+
|
|
|
+```
|
|
|
+相比 Vue2,Vue3 整体体积变小了,除了移出一些不常用的 API,再重要的是 Tree
|
|
|
+shanking
|
|
|
+任何一个函数,如 ref、reavtived、computed 等,仅仅在用到的时候才打包,没
|
|
|
+用到的模块都被摇掉,打包的整体体积变小
|
|
|
+```
|
|
|
+<script>
|
|
|
+ // 假设这是你的某个组件文件
|
|
|
+ import { ref } from 'vue';
|
|
|
+
|
|
|
+ export default {
|
|
|
+ setup() {
|
|
|
+ // 只使用了ref函数,没有使用reactive或computed
|
|
|
+ const count = ref(0);
|
|
|
+
|
|
|
+ function increment() {
|
|
|
+ count.value++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ count,
|
|
|
+ increment
|
|
|
+ };
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 在上面的例子中,因为只使用了ref函数,所以打包工具在压缩和优化时,理论上会排除reactive和computed相关代码,从而减小最终bundle的体积。
|
|
|
+</script>
|
|
|
+
|
|
|
+```
|
|
|
+ Tree shanking的作用
|
|
|
+ 1、减少程序体积(更小)
|
|
|
+ 2、减少程序执行时间(更快)
|
|
|
+ 3、便于将来对程序架构进行优化(更友好)
|
|
|
+```
|
|
|
+
|
|
|
+### 响应式系统
|
|
|
+<script>
|
|
|
+ // Object.defineProperty 只能遍历对象属性进行劫持
|
|
|
+function observe(obj) {
|
|
|
+ if (typeof obj !== 'object' || obj == null) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ Object.keys(obj).forEach(key => {
|
|
|
+ defineReactive(obj, key, obj[key])
|
|
|
+ })
|
|
|
+}
|
|
|
+// Proxy 直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
|
|
|
+function reactive(obj) {
|
|
|
+ if (typeof obj !== 'object' && obj != null) {
|
|
|
+ return obj
|
|
|
+ }
|
|
|
+ // Proxy 相当于在对象外层加拦截
|
|
|
+ const observed = new Proxy(obj, {
|
|
|
+ get(target, key, receiver) {
|
|
|
+ const res = Reflect.get(target, key, receiver)
|
|
|
+ console.log(`获取${key}:${res}`)
|
|
|
+ return res
|
|
|
+ },
|
|
|
+ set(target, key, value, receiver) {
|
|
|
+ const res = Reflect.set(target, key, value, receiver)
|
|
|
+ console.log(`设置${key}:${value}`)
|
|
|
+ return res
|
|
|
+ },
|
|
|
+ deleteProperty(target, key) {
|
|
|
+ const res = Reflect.deleteProperty(target, key)
|
|
|
+ console.log(`删除${key}:${res}`)
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return observed
|
|
|
+}
|
|
|
+// Proxy 可以直接监听数组的变化(push、shift、splice)
|
|
|
+const obj = [1,2,3]
|
|
|
+const proxtObj = reactive(obj)
|
|
|
+obj.push(4) // ok
|
|
|
+// Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等,
|
|
|
+// Proxy 不兼容 IE,也没有 polyfill, defineProperty 能支持到 IE9
|
|
|
+// 这是 Object.defineProperty 不具备的
|
|
|
+// 正因为 defineProperty 自身的缺陷,导致 Vue2 在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外 set、delete 方法)
|
|
|
+// 数组重写
|
|
|
+const originalProto = Array.prototype
|
|
|
+const arrayProto = Object.create(originalProto)['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort']
|
|
|
+.forEach(method => {
|
|
|
+ arrayProto[method] = function () {
|
|
|
+ originalProto[method].apply(this.arguments)
|
|
|
+ dep.notice()
|
|
|
+ }
|
|
|
+});
|
|
|
+// set、delete
|
|
|
+Vue.set(obj,'bar','newbar')
|
|
|
+Vue.delete(obj,'bar')
|
|
|
+</script>
|
|
|
+
|
|
|
+## 状态管理 pinia
|
|
|
+
|
|
|
+> 由于 vuex 4 对 typescript 的支持度较低,所以状态管理弃用了 vuex 而采取了 pinia. pinia 的作者是 Vue 核心团队成员
|
|
|
+
|
|
|
+尤大好像说 `pinia` 可能会代替 `vuex`,所以请放心使用。
|
|
|
+
|
|
|
+### 安装 pinia
|
|
|
+
|
|
|
+Pinia 与 Vuex 的区别:
|
|
|
+
|
|
|
+- `id` 是必要的,它将所使用 store 连接到 devtools。
|
|
|
+- 创建方式:`new Vuex.Store(...)`(vuex3),`createStore(...)`(vuex4)。
|
|
|
+- 对比于 vuex3 ,state 现在是一个`函数返回对象`。
|
|
|
+- 没有 `mutations`,不用担心,state 的变化依然记录在 devtools 中。
|
|
|
+
|
|
|
+```bash
|
|
|
+# 安装
|
|
|
+yarn add pinia@next
|
|
|
+```
|
|
|
+
|
|
|
+main.ts 中增加
|
|
|
+
|
|
|
+```js
|
|
|
+# 引入
|
|
|
+import { createPinia } from "pinia"
|
|
|
+# 创建根存储库并将其传递给应用程序
|
|
|
+app.use(createPinia())
|
|
|
+```
|
|
|
+
|
|
|
+在 `src` 文件夹下新增 `store` 文件夹,接在在 store 中新增 `main.ts`
|
|
|
+
|
|
|
+### 创建 `store`, main.ts :
|
|
|
+
|
|
|
+```js
|
|
|
+import { defineStore } from 'pinia'
|
|
|
+
|
|
|
+export const useMainStore = defineStore({
|
|
|
+ id: 'main',
|
|
|
+ state: () => ({
|
|
|
+ name: '超级管理员',
|
|
|
+ }),
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+组建中获取 store :
|
|
|
+
|
|
|
+```js
|
|
|
+<template>
|
|
|
+ <div>{{mainStore.name}}</div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { useMainStore } from "@/store/main"
|
|
|
+
|
|
|
+const mainStore = useMainStore()
|
|
|
+
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+### getters 用法介绍
|
|
|
+
|
|
|
+> Pinia 中的 getter 与 Vuex 中的 getter 、组件中的计算属性具有相同的功能
|
|
|
+
|
|
|
+`store` => `main.ts`
|
|
|
+
|
|
|
+```js
|
|
|
+import { defineStore } from 'pinia'
|
|
|
+
|
|
|
+export const useMainStore = defineStore({
|
|
|
+ id: 'main',
|
|
|
+ state: () => ({
|
|
|
+ name: '超级管理员',
|
|
|
+ }),
|
|
|
+ // getters
|
|
|
+ getters: {
|
|
|
+ nameLength: (state) => state.name.length,
|
|
|
+ },
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+组件中使用:
|
|
|
+
|
|
|
+```js
|
|
|
+<template>
|
|
|
+ <div>用户名:{{ mainStore.name }}<br />长度:{{ mainStore.nameLength }}</div>
|
|
|
+ <hr/>
|
|
|
+ <button @click="updateName">修改store中的name</button>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { useMainStore } from '@/store/main'
|
|
|
+
|
|
|
+const mainStore = useMainStore()
|
|
|
+
|
|
|
+const updateName = ()=>{
|
|
|
+ // $patch 修改 store 中的数据
|
|
|
+ mainStore.$patch({
|
|
|
+ name: '名称被修改了,nameLength也随之改变了'
|
|
|
+ })
|
|
|
+}
|
|
|
+</script>
|
|
|
+/* $patch 方法在 Vue.js 或者与 Vue 生态相关的状态管理库(如 Vuex 或 Pinia)中常用于局部更新或合并现有状态。这个方法允许你以部分更新的方式修改 store 中的状态,而无需替换整个状态对象。
|
|
|
+
|
|
|
+在 Pinia(Vue3 的状态管理库)中,store.$patch 方法用来直接对 store 状态进行部分更新,例如: */
|
|
|
+
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+### actions
|
|
|
+
|
|
|
+> 这里与 `Vuex` 有极大的不同,`Pinia` 仅提供了一种方法来定义如何更改状态的规则,放弃 `mutations` 只依靠 `Actions`,这是一项重大的改变。
|
|
|
+
|
|
|
+`Pinia` 让 `Actions` 更加的灵活:
|
|
|
+
|
|
|
+- 可以通过组件或其他 `action` 调用
|
|
|
+- 可以从其他 `store` 的 `action` 中调用
|
|
|
+- 直接在 `store` 实例上调用
|
|
|
+- 支持`同步`或`异步`
|
|
|
+- 有任意数量的参数
|
|
|
+- 可以包含有关如何更改状态的逻辑(也就是 vuex 的 mutations 的作用)
|
|
|
+- 可以 `$patch` 方法直接更改状态属性
|
|
|
+
|
|
|
+```ts
|
|
|
+import { defineStore } from 'pinia'
|
|
|
+
|
|
|
+export const useMainStore = defineStore({
|
|
|
+ id: 'main',
|
|
|
+ state: () => ({
|
|
|
+ name: '超级管理员',
|
|
|
+ }),
|
|
|
+ getters: {
|
|
|
+ nameLength: (state) => state.name.length,
|
|
|
+ },
|
|
|
+ actions: {
|
|
|
+ async insertPost(data: string) {
|
|
|
+ // 可以做异步
|
|
|
+ // await doAjaxRequest(data);
|
|
|
+ this.name = data
|
|
|
+ },
|
|
|
+ },
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+## composition API 与 options API
|
|
|
+```
|
|
|
+在 Vue2 中,我们用过 mixin 去复用相同的逻辑
|
|
|
+```
|
|
|
+<script>
|
|
|
+export const MoveMixin = {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handleKeyup(e) {
|
|
|
+ console.log(e.code);
|
|
|
+ // 上下左右 x y
|
|
|
+ switch (e.code) {
|
|
|
+ case "ArrowUp":
|
|
|
+ this.y--;
|
|
|
+ break;
|
|
|
+ case "ArrowDown":
|
|
|
+ this.y++;
|
|
|
+ break;
|
|
|
+ case "ArrowLeft":
|
|
|
+ this.x--;
|
|
|
+ break;
|
|
|
+ case "ArrowRight":
|
|
|
+ this.x++;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ window.addEventListener("keyup", this.handleKeyup);
|
|
|
+ },
|
|
|
+ unmounted() {
|
|
|
+ window.removeEventListener("keyup", this.handleKeyup);
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+然后在组件中使用
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ Mouse position: x {{ x }} / y {{ y }}
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+import mousePositionMixin from './mouse'
|
|
|
+export default {
|
|
|
+ mixins: [mousePositionMixin]
|
|
|
+}
|
|
|
+</script>
|
|
|
+使用单个 mixin 似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时
|
|
|
+候
|
|
|
+mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]
|
|
|
+会存在两个非常明显的问题:
|
|
|
+• 命名冲突
|
|
|
+• 数据来源不清晰
|
|
|
+现在通过 Compositon API 这种方式改写上面的代码
|
|
|
+<script>
|
|
|
+ import { onMounted, onUnmounted, reactive } from "vue";
|
|
|
+export function useMove() {
|
|
|
+ const position = reactive({
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ });
|
|
|
+ const handleKeyup = (e) => {
|
|
|
+ console.log(e.code);
|
|
|
+ // 上下左右 x y
|
|
|
+ switch (e.code) {
|
|
|
+ case "ArrowUp":
|
|
|
+ position.y--;
|
|
|
+ break;
|
|
|
+ case "ArrowDown":
|
|
|
+ position.y++;
|
|
|
+ break;
|
|
|
+ case "ArrowLeft":
|
|
|
+ position.x--;
|
|
|
+ break;
|
|
|
+ case "ArrowRight":
|
|
|
+ position.x++;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ onMounted(() => {
|
|
|
+ window.addEventListener("keyup", handleKeyup);
|
|
|
+ });
|
|
|
+ onUnmounted(() => {
|
|
|
+ window.removeEventListener("keyup", handleKeyup);
|
|
|
+ });
|
|
|
+ return { position };
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ Mouse position: x {{ x }} / y {{ y }}
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+// toRefs将state对象的属性分别转换成了独立的ref对象,这样在模板中可以直接通过.value访问它们的值,而且更改.value会触发视图更新。
|
|
|
+import { useMove } from "./useMove";
|
|
|
+import { toRefs } from "vue";
|
|
|
+position = {x, y}
|
|
|
+torefs() => ref(x) ref(y)
|
|
|
+export default {
|
|
|
+ setup() {
|
|
|
+ const { position } = useMove();
|
|
|
+ const { x, y } = toRefs(position);
|
|
|
+ return {
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ };
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|