vue3.md 18 KB

Vue2 与 Vue3 的区别

Vue3由于完全由TS进行重写,在应用中对类型判断的定义和使用有很强的表现。同一对象的多个键返回值必须通过定义对应的接口(interface)来进行类型定义。要不然在 ESLint 时都会报错。

vue2 的双向数据绑定是利用 ES5 的一个 API Object.definePropert()对数据进行劫持 结合 发布订阅模式的方式来实现的。Vue3 中使用了 es6ProxyAPI 对数据代理。

Vue3支持碎片(Fragments)

Vue2 与 Vue3 最大的区别: Vue2 使用Options API而 Vue3 使用的Composition API Composition API 中,所有的逻辑可以集中在一个名为 setup() 的函数内定义,并根据功能进行分组和模块化。

声明响应式状态的方式

ref() reactive() shallowRef() shallowReactive()

生命周期钩子变化

Vue2 ~~~~~~~~~~~ vue3
beforeCreate  -> setup()
created       -> setup()  // 实例没有创建,不能访问上下文
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated

{{ message }}

性能提升

vue3 => {
  编译阶段
  源码体积
  响应式系统
}

• diff 算法优化
• 静态提升
• 事件监听缓存
• SSR 优化

静态文本

静态文本

{{ message }}

静态文本

...

静态文本

1.diff 算法

vue3 在 diff 算法中相比 vue2 增加了静态标记
关于这个静态标记,其作用是为了会发生变化的地方添加一个 flag 标记,下次发
生变化的时候直接找该地方进行比较

2.静态提升

Vue3 中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复
用
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操
作,优化了运行时候的内存占用

你好

{{ message }}


### 事件监听缓存

未开启事件监听器缓存:

<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

{{mainStore.name}}

``` ### 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
用户名:{{ mainStore.name }}
长度:{{ mainStore.nameLength }}

修改store中的name /* $patch 方法在 Vue.js 或者与 Vue 生态相关的状态管理库(如 Vuex 或 Pinia)中常用于局部更新或合并现有状态。这个方法允许你以部分更新的方式修改 store 中的状态,而无需替换整个状态对象。 在 Pinia(Vue3 的状态管理库)中,store.$patch 方法用来直接对 store 状态进行部分更新,例如: */ ``` ![](https://files.mdnice.com/user/16854/ab70ded8-aa34-456a-9044-ac560ff5d2.gif) ### 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 去复用相同的逻辑 ``` 然后在组件中使用
Mouse position: x {{ x }} / y {{ y }}
使用单个 mixin 似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时 候 mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin] 会存在两个非常明显的问题: • 命名冲突 • 数据来源不清晰 现在通过 Compositon API 这种方式改写上面的代码

Mouse position: x {{ x }} / y {{ y }}