33 Vue3相关(1/2)

33 Vue3相关

Vue3 对 Vue2 有什么优势

  • 性能更好(编译优化、使用proxy等)
  • 体积更小
  • 更好的TS支持
  • 更好的代码组织
  • 更好的逻辑抽离
  • 更多新功能

Vue3 和 Vue2 的生命周期有什么区别

Options API生命周期

  • beforeDestroy改为beforeUnmount
  • destroyed改为unmounted
  • 其他沿用vue2生命周期

Composition API生命周期

    import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
    
    export default {
        name: 'LifeCycles',
        props: {
          msg: String
        },
        // setup等于 beforeCreate 和 created
        setup() {
            console.log('setup')
    
            onBeforeMount(() => {
                console.log('onBeforeMount')
            })
            onMounted(() => {
                console.log('onMounted')
            })
            onBeforeUpdate(() => {
                console.log('onBeforeUpdate')
            })
            onUpdated(() => {
                console.log('onUpdated')
            })
            onBeforeUnmount(() => {
                console.log('onBeforeUnmount')
            })
            onUnmounted(() => {
                console.log('onUnmounted')
            })
        },
    
        // 兼容vue2生命周期 options API和composition API生命周期二选一
        beforeCreate() {
            console.log('beforeCreate')
        },
        created() {
            console.log('created')
        },
        beforeMount() {
            console.log('beforeMount')
        },
        mounted() {
            console.log('mounted')
        },
        beforeUpdate() {
            console.log('beforeUpdate')
        },
        updated() {
            console.log('updated')
        },
        // beforeDestroy 改名
        beforeUnmount() {
            console.log('beforeUnmount')
        },
        // destroyed 改名
        unmounted() {
            console.log('unmounted')
        }
    }

Vue3如何实现响应式

  • 回顾vue2Object.defineProperty
  • 缺点
    • 深度监听对象需要一次性递归
    • 无法监听新增属性、删除属性(Vue.setVue.delete)
    • 无法监听原生数组,需要特殊处理
  • 学习proxy语法
  • Vue3中如何使用proxy实现响应式

Proxy 基本使用

    // const data = {
    //     name: 'zhangsan',
    //     age: 20,
    // }
    const data = ['a', 'b', 'c']
    
    const proxyData = new Proxy(data, {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('get', key) // 监听
            }
    
            const result = Reflect.get(target, key, receiver)
            return result // 返回结果
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true
            }
    
            const result = Reflect.set(target, key, val, receiver)
            console.log('set', key, val)
            // console.log('result', result) // true
            return result // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key)
            console.log('delete property', key)
            // console.log('result', result) // true
            return result // 是否删除成功
        }
    })

vue3用Proxy 实现响应式

  • 深度监听,性能更好(获取到哪一层才触发响应式get,不是一次性递归)
  • 可监听新增/删除属性
  • 可监听数组变化
    // 创建响应式
    function reactive(target = {}) {
      if (typeof target !== 'object' || target == null) {
          // 不是对象或数组,则返回
          return target
      }
    
      // 代理配置
      const proxyConf = {
          get(target, key, receiver) {
              // 只处理本身(非原型的)属性
              const ownKeys = Reflect.ownKeys(target)
              if (ownKeys.includes(key)) {
                  console.log('get', key) // 监听
              }
      
              const result = Reflect.get(target, key, receiver)
          
              // 深度监听
              // 性能如何提升的?获取到哪一层才触发响应式get,不是一次性递归
              return reactive(result)
          },
          set(target, key, val, receiver) {
              // 重复的数据,不处理
              if (val === target[key]) {
                  return true
              }
      
              const ownKeys = Reflect.ownKeys(target)
              if (ownKeys.includes(key)) {
                  console.log('已有的 key', key)
              } else {
                  console.log('新增的 key', key)
              }
    
              const result = Reflect.set(target, key, val, receiver)
              console.log('set', key, val)
              // console.log('result', result) // true
              return result // 是否设置成功
          },
          deleteProperty(target, key) {
              const result = Reflect.deleteProperty(target, key)
              console.log('delete property', key)
              // console.log('result', result) // true
              return result // 是否删除成功
          }
      }
    
      // 生成代理对象
      const observed = new Proxy(target, proxyConf)
      return observed
    }
    
    // 测试数据
    const data = {
      name: 'zhangsan',
      age: 20,
      info: {
          city: 'shenshen',
          a: {
              b: {
                  c: {
                      d: {
                          e: 100
                      }
                  }
              }
          }
      }
    }
    
    const proxyData = reactive(data)

如何理解Composition API和Options API

  • Composition API带来了什么
    • 更好的代码组织
    • 更好的逻辑复用
    • 更好的类型推导
  • Composition APIOptions API如何选择
    • 不建议共用,会引起混乱
    • 小型项目、业务逻辑简单,用Option API成本更小一些
    • 中大型项目、逻辑复杂,用Composition API

Composition API 如何实现逻辑复用

  • 抽离逻辑代码到一个函数
  • 函数命名约定为useXx格式(React Hooks也是)
  • setup中引用useXx函数
    <template>
        <p>mouse position {{x}} {{y}}</p>
    </template>
    
    <script>
    import { reactive } from 'vue'
    import useMousePosition from './useMousePosition'
    // import useMousePosition2 from './useMousePosition'
    
    export default {
        name: 'MousePosition',
        setup() {
            const { x, y } = useMousePosition()
            return {
                x,
                y
            }
    
            // const state = useMousePosition2()
            // return {
            //     state
            // }
        }
    }
    </script>
    import { reactive, ref, onMounted, onUnmounted } from 'vue'
    
    function useMousePosition() {
        const x = ref(0)
        const y = ref(0)
    
        function update(e) {
            x.value = e.pageX
            y.value = e.pageY
        }
    
        onMounted(() => {
            console.log('useMousePosition mounted')
            window.addEventListener('mousemove', update)
        })
    
        onUnmounted(() => {
            console.log('useMousePosition unMounted')
            window.removeEventListener('mousemove', update)
        })
    
        // 合成函数尽量返回ref或toRefs(state)  state = reactive({})
        // 这样在使用的时候可以解构但不丢失响应式
        return {
            x,
            y
        }
    }
    
    // function useMousePosition2() {
    //     const state = reactive({
    //         x: 0,
    //         y: 0
    //     })
    
    //     function update(e) {
    //         state.x = e.pageX
    //         state.y = e.pageY
    //     }
    
    //     onMounted(() => {
    //         console.log('useMousePosition mounted')
    //         window.addEventListener('mousemove', update)
    //     })
    
    //     onUnmounted(() => {
    //         console.log('useMousePosition unMounted')
    //         window.removeEventListener('mousemove', update)
    //     })
    
    //     return state
    // }
    
    export default useMousePosition
    // export default useMousePosition2

Composition API 和 React Hooks 的对比

  • Composition APIsetup(相当于createdbeforeCreate的合集)只会调用一次,而React Hooks函数在渲染过程中会被多次调用
  • Composition API无需使用useMemouseCallback避免子组件重复渲染,因为setup只会调用一次,在setup闭包中缓存了变量
  • Composition API无需顾虑调用顺序,而React Hooks需要保证hooks的顺序一致(比如不能放在循环、判断里面)
  • Composition APIrefreactiveuseState难理解

Vue3的设计目标是什么?做了哪些优化

1、设计目标

不以解决实际业务痛点的更新都是耍流氓,下面我们来列举一下Vue3之前我们或许会面临的问题

  • 随着功能的增长,复杂组件的代码变得越来越难以维护
  • 缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制
  • 类型推断不够友好
  • bundle的时间太久了

Vue3 经过长达两三年时间的筹备,做了哪些事情?

我们从结果反推

  • 更小
  • 更快
  • TypeScript支持
  • API设计一致性
  • 提高自身可维护性
  • 开放更多底层功能

一句话概述,就是更小更快更友好了

更小

  • Vue3移除一些不常用的 API

  • 引入tree-shaking,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了

更快

主要体现在编译方面:

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

更友好

vue3在兼顾vue2options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码复用能力

这里代码简单演示下:

存在一个获取鼠标位置的函数

    import { toRefs, reactive } from 'vue';
    function useMouse(){
        const state = reactive({x:0,y:0});
        const update = e=>{
            state.x = e.pageX;
            state.y = e.pageY;
        }
        onMounted(()=>{
            window.addEventListener('mousemove',update);
        })
        onUnmounted(()=>{
            window.removeEventListener('mousemove',update);
        })
    
        return toRefs(state);
    }

我们只需要调用这个函数,即可获取xy的坐标,完全不用关注实现过程

试想一下,如果很多类似的第三方库,我们只需要调用即可,不必关注实现过程,开发效率大大提高

同时,VUE3是基于typescipt编写的,可以享受到自动的类型定义提示

2、优化方案

vue3从很多层面都做了优化,可以分成三个方面:

  • 源码
  • 性能
  • 语法 API

源码

源码可以从两个层面展开:

  • 源码管理
  • TypeScript

源码管理

vue3整个源码是通过 monorepo的方式维护的,根据功能将不同的模块拆分到packages目录下面不同的子目录中

这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性

另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue 使用的,这样用户如果只想使用 Vue3的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue

TypeScript

Vue3是基于typeScript编写的,提供了更好的类型检查,能支持复杂的类型推导

性能

vue3是从什么哪些方面对性能进行进一步优化呢?

  • 体积优化
  • 编译优化
  • 数据劫持优化

这里讲述数据劫持:

vue2中,数据劫持是通过Object.defineProperty,这个 API 有一些缺陷,并不能检测对象属性的添加和删除

    Object.defineProperty(data, 'a',{
      get(){
        // track
      },
      set(){
        // trigger
      }
    })

尽管Vue为了解决这个问题提供了 setdelete实例方法,但是对于用户来说,还是增加了一定的心智负担

同时在面对嵌套层级比较深的情况下,就存在性能问题

    default {
      data: {
        a: {
          b: {
              c: {
              d: 1
            }
          }
        }
      }
    }

相比之下,vue3是通过proxy监听整个对象,那么对于删除还是监听当然也能监听到

同时Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归

语法 API

这里当然说的就是composition API,其两大显著的优化:

  • 优化逻辑组织
  • 优化逻辑复用

逻辑组织

一张图,我们可以很直观地感受到 Composition API在逻辑组织方面的优势

相同功能的代码编写在一块,而不像options API那样,各个功能的代码混成一块

逻辑复用

vue2中,我们是通过mixin实现功能混合,如果多个mixin混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰

而通过composition这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可

同样是上文的获取鼠标位置的例子

    import { toRefs, reactive, onUnmounted, onMounted } from 'vue';
    function useMouse(){
        const state = reactive({x:0,y:0});
        const update = e=>{
            state.x = e.pageX;
            state.y = e.pageY;
        }
        onMounted(()=>{
            window.addEventListener('mousemove',update);
        })
        onUnmounted(()=>{
            window.removeEventListener('mousemove',update);
        })
    
        return toRefs(state);
    }

组件使用

    import useMousePosition from './mouse'
    export default {
        setup() {
            const { x, y } = useMousePosition()
            return { x, y }
        }
    }

可以看到,整个数据来源清晰了,即使去编写更多的hook函数,也不会出现命名冲突的问题

Vue3有了解过吗?能说说跟vue2的区别吗?

1. 哪些变化

从上图中,我们可以概览Vue3的新特性,如下:

  • 速度更快
  • 体积减少
  • 更易维护
  • 更接近原生
  • 更易使用

1.1 速度更快

vue3相比vue2

  • 重写了虚拟Dom实现
  • 编译模板的优化
  • 更高效的组件初始化
  • undate性能提高1.3~2倍
  • SSR速度提高了2~3倍

1.2 体积更小

通过webpacktree-shaking功能,可以将无用模块“剪辑”,仅打包需要的

能够tree-shaking,有两大好处:

  • 对开发人员,能够对vue实现更多其他的功能,而不必担忧整体体积过大
  • 对使用者,打包出来的包体积变小了

vue可以开发出更多其他的功能,而不必担忧vue打包出来的整体体积过多

1.3 更易维护

compositon Api

  • 可与现有的Options API一起使用
  • 灵活的逻辑组合与复用
  • Vue3模块可以和其他框架搭配使用

更好的Typescript支持

VUE3是基于typescipt编写的,可以享受到自动的类型定义提示

1.4 编译器重写

1.5 更接近原生

可以自定义渲染 API

1.6 更易使用

响应式 Api 暴露出来

轻松识别组件重新渲染原因

2. Vue3新增特性

Vue 3 中需要关注的一些新功能包括:

  • framents
  • Teleport
  • composition Api
  • createRenderer

2.1 framents

Vue3.x 中,组件现在支持有多个根节点

    <!-- Layout.vue -->
    <template>
      <header>...</header>
      <main v-bind="$attrs">...</main>
      <footer>...</footer>
    </template>

2.2 Teleport

Teleport 是一种能够将我们的模板移动到 DOMVue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”

vue2中,像 modals,toast 等这样的元素,如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难

通过Teleport,我们可以在组件的逻辑位置写模板代码,然后在 Vue 应用范围之外渲染它

    <button @click="showToast" class="btn">打开 toast</button>
    <!-- to 属性就是目标位置 -->
    <teleport to="#teleport-target">
        <div v-if="visible" class="toast-wrap">
            <div class="toast-msg">我是一个 Toast 文案</div>
        </div>
    </teleport>

2.3 createRenderer

通过createRenderer,我们能够构建自定义渲染器,我们能够将 vue 的开发模型扩展到其他平台

我们可以将其生成在canvas画布上

关于createRenderer,我们了解下基本使用,就不展开讲述了

    import { createRenderer } from '@vue/runtime-core'
    
    const { render, createApp } = createRenderer({
      patchProp,
      insert,
      remove,
      createElement,
      // ...
    })
    
    export { render, createApp }
    
    export * from '@vue/runtime-core'

2.4 composition Api

composition Api,也就是组合式api,通过这种形式,我们能够更加容易维护我们的代码,将相同功能的变量进行一个集中式的管理

关于compositon api的使用,这里以下图展开

简单使用:

    export default {
        setup() {
            const count = ref(0)
            const double = computed(() => count.value * 2)
            function increment() {
                count.value++
            }
            onMounted(() => console.log('component mounted!'))
            return {
                count,
                double,
                increment
            }
        }
    }

3. 非兼容变更

3.1 Global API

  • 全局 Vue API 已更改为使用应用程序实例
  • 全局和内部 API 已经被重构为可 tree-shakable

3.2 模板指令

  • 组件上 v-model 用法已更改
  • <template v-for>和 非 v-for节点上key用法已更改
  • 在同一元素上使用的 v-ifv-for 优先级已更改
  • v-bind="object" 现在排序敏感
  • v-for 中的 ref 不再注册 ref 数组

3.3 组件

  • 只能使用普通函数创建功能组件
  • functional 属性在单文件组件 (SFC)
  • 异步组件现在需要 defineAsyncComponent 方法来创建

3.4 渲染函数

  • 渲染函数API改变
  • $scopedSlots property 已删除,所有插槽都通过 $slots 作为函数暴露
  • 自定义指令 API 已更改为与组件生命周期一致
  • 一些转换class被重命名了:
    • v-enter -> v-enter-from
    • v-leave -> v-leave-from
  • 组件 watch 选项和实例方法 $watch不再支持点分隔字符串路径,请改用计算函数作为参数
  • Vue 2.x 中,应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。VUE3.x 现在使用应用程序容器的 innerHTML

3.5 其他小改变

  • destroyed 生命周期选项被重命名为 unmounted
  • beforeDestroy 生命周期选项被重命名为 beforeUnmount
  • prop default工厂函数不再有权访问 this 是上下文
  • 自定义指令 API 已更改为与组件生命周期一致
  • data 应始终声明为函数
  • 来自 mixindata 选项现在可简单地合并
  • attribute 强制策略已更改
  • 一些过渡 class 被重命名
  • 组建 watch 选项和实例方法 $watch不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。
  • <template> 没有特殊指令的标记 (v-if/else-if/elsev-forv-slot) 现在被视为普通元素,并将生成原生的 <template> 元素,而不是渲染其内部内容。
  • Vue 2.x 中,应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x 现在使用应用容器的 innerHTML,这意味着容器本身不再被视为模板的一部分。

3.6 移除 API

  • keyCode 支持作为 v-on 的修饰符
  • $on$off$once 实例方法
  • 过滤filter
  • 内联模板 attribute
  • $destroy 实例方法。用户不应再手动管理单个Vue 组件的生命周期。

你知道哪些Vue3新特性?

官网列举的最值得注意的新特性:v3-migration.vuejs.org (opens new window)

  • Composition API
  • SFC Composition API语法糖
  • Teleport传送门
  • Fragments片段
  • Emits选项
  • 自定义渲染器
  • SFC CSS变量
  • Suspense

以上这些是api相关,另外还有很多框架特性也不能落掉

回答范例

  1. api层面Vue3新特性主要包括:Composition APISFC Composition API语法糖、Teleport传送门、Fragments 片段、Emits选项、自定义渲染器、SFC CSS变量、Suspense
  2. 另外,Vue3.0在框架层面也有很多亮眼的改进:
  • 更快
    • 虚拟DOM重写,diff算法优化
    • 编译器优化:静态提升、patchFlags(静态标记)、事件监听缓存
    • 基于Proxy的响应式系统
    • SSR优化
  • 更小 :更好的摇树优化 tree shakingVue3移除一些不常用的 API
  • 更友好vue3在兼顾vue2options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码复用能力
  • 更容易维护TypeScript + 模块化
  • 更容易扩展
    • 独立的响应化模块
    • 自定义渲染器

Vue3速度快的原因

Vue3.0 性能提升体现在哪些方面

  • 代码层面性能优化主要体现在全新响应式API,基于Proxy实现,性能更好(获取到哪一层才触发响应式get,不是像vue2一次性递归监听数据)

  • 编译层面做了更多编译优化处理,比如静态标记 pachFlagdiff算法增加了一个静态标记,只对比有标记的dom元素)、事件增加缓存静态提升(对不参与更新的元素,会做静态提升,只会被创建一次,之后会在每次渲染时候被不停的复用)等,可以有效跳过大量diff过程;

  • 打包时更好的支持tree-shaking,因此整体体积更小,加载更快

  • ssr渲染以字符串方式渲染

  • proxy响应式:深度监听,性能更好(获取到哪一层才触发响应式get,不是一次性递归)

  • PatchFlag 动态节点做标志

  • HoistStatic 将静态节点的定义,提升到父作用域,缓存起来。多个相邻的静态节点,会被合并起来

  • CacheHandler 事件缓存

  • SSR优化: 静态节点不走vdom逻辑,直接输出字符串

  • Tree-shaking 根据模板的内容动态import不同的内容,不需要就不import

一、编译阶段

试想一下,一个组件结构如下图

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

可以看到,组件内部只有一个动态节点,剩余一堆都是静态节点,所以这里很多 diff 和遍历其实都是不需要的,造成性能浪费

因此,Vue3在编译阶段,做了进一步优化。主要有如下:

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

1. diff 算法优化

  • Vue 2x 中的虚拟 dom 是进行全量的对比。
  • Vue 3x 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化

什么是PatchFlag

  • 模板编译时,动态节点做标记
  • 标记,分为不同类型,如TextPROPSCLASS
  • diff算法时,可区分静态节点,以及不同类型的动态节点

    <!-- https://vue-next-template-explorer.netlify.app 中打开查看编译结果 -->
    
    <div>
      <span>hello vue3</span>
      <span>{{msg}}</span>
      <span :class="name">poetry</span>
      <span :id="name">poetry</span>
      <span :id="name">{{msg}}</span>
      <span :id="name" :msg="msg">poetry</span>
    </div>
    // 编译后结果
    
    import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, [
        _createElementVNode("span", null, "hello vue3"),
        _createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */), // 文本标记1
        _createElementVNode("span", {
          class: _normalizeClass(_ctx.name)
        }, "poetry", 2 /* CLASS */), // class标记2
        _createElementVNode("span", { id: _ctx.name }, "poetry", 8 /* PROPS */, ["id"]), // 属性props标记8
        _createElementVNode("span", { id: _ctx.name }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["id"]), // 文本和属性组合标记9
        _createElementVNode("span", {
          id: _ctx.name,
          msg: _ctx.msg
        }, "poetry", 8 /* PROPS */, ["id", "msg"]) // 属性组合标记
      ]))
    }

Vue2.x的diff算法

vue2.xdiff算法叫做全量比较,顾名思义,就是当数据改变的时候,会从头到尾的进行vDom对比,即使有些内容是永恒固定不变的

Vue3.0的diff算法

vue3.0diff算法有个叫静态标记(PatchFlag)的小玩意,啥是静态标记呢?简单点说,就是如果你的内容会变,我会给你一个flag,下次数据更新的时候我直接来对比你,我就不对比那些没有标记的了

已经标记静态节点的p标签在diff过程中则不会比较,把性能进一步提高

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
     return (_openBlock(), _createBlock("div", null, [
      _createVNode("p", null, "'HelloWorld'"),
      _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
                            //上面这个1就是静态标记
     ]))
    }

关于静态类型枚举如下

    TEXT = 1 // 动态文本节点
    CLASS=1<<1,1 // 2//动态class
    STYLE=1<<2// 4 //动态style
    PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
    FULLPR0PS=1<<4,// 16 //具有动态key属性,当key改变时,需要进行完整的diff比较。
    HYDRATE_ EVENTS = 1 << 5// 32 //带有监听事件的节点
    STABLE FRAGMENT = 1 << 6, // 64 //一个不会改变子节点顺序的fragment
    KEYED_ FRAGMENT = 1 << 7, // 128 //带有key属性的fragment 或部分子字节有key
    UNKEYED FRAGMENT = 1<< 8, // 256 //子节点没有key 的fragment
    NEED PATCH = 1 << 9, // 512 //一个节点只会进行非props比较
    DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot
    HOISTED = -1 // 静态节点
    // 指示在diff算法中退出优化模式
    BALL = -2

2. hoistStatic 静态提升

  • Vue 2x : 无论元素是否参与更新,每次都会重新创建。
  • Vue 3x : 对不参与更新的元素,会做静态提升,只会被创建一次,之后会在每次渲染时候被不停的复用。这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
  • 将静态节点的定义,提升到父作用域,缓存起来
  • 多个相邻的静态节点,会被合并起来
  • 典型的拿空间换时间的优化策略
    <p>HelloWorld</p>
    <p>HelloWorld</p>
    
    <p>{ message }</p>

开启静态提升前

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
     return (_openBlock(), _createBlock("div", null, [
      _createVNode("p", null, "'HelloWorld'"),
      _createVNode("p", null, "'HelloWorld'"),
      _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
     ]))
    }

开启静态提升后编译结果

    // https://vue-next-template-explorer.netlify.app 中打开查看编译结果
    
    // 之后函数怎么执行,这些变量都不会被重复定义一遍
    const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)
    const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
     return (_openBlock(), _createBlock("div", null, [
      _hoisted_1,
      _hoisted_2,
      _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
     ]))
    }

可以看到开启了静态提升后,直接将那两个内容为helloworldp标签声明在外面了,直接就拿来用了。同时 _hoisted_1_hoisted_2 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff

    <!-- https://vue-next-template-explorer.netlify.app 中打开查看编译结果:options开启hoistStatic -->
    <!-- 当相同的节点达到一定阈值后会被vue3合并起来 -->
    <div>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>hello vue3</span>
      <span>{{msg}}</span>
    </div>
    // 编译之后
    
    import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createStaticVNode as _createStaticVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
    
    // 多个相邻的静态节点,会被合并起来
    const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span>", 10)
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, [
        _hoisted_1,
        _createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
      ]))
    }

3. cacheHandlers 事件监听缓存

  • 默认情况下绑定事件会被视为动态绑定 ,所以每次都会去追踪它的变化
  • 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可
    <!-- https://vue-next-template-explorer.netlify.app 中打开查看编译结果:options开启cacheHandler -->
    <div>
     <button @click = 'onClick'>点我</button>
    </div>

开启事件侦听器缓存之前:

    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"])
                           // PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
     ]))
    })

这里有一个8,表示着这个节点有了静态标记,有静态标记就会进行diff算法对比差异,所以会浪费时间

开启事件侦听器缓存之后:

    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)))
      }, "点我")
     ]))
    }

上述发现开启了缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用

4. SSR优化

  • 静态节点直接输出,绕过了vdom
  • 动态节点,还是需要动态渲染

当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染

    <!-- https://vue-next-template-explorer.netlify.app 中打开查看编译结果:options开启ssr -->
    <div>
      <span>hello vue3</span>
      <span>hello vue3</span> 
      <span>hello vue3</span> 
      <!-- 这里有很多个静态节点... -->
      <span>{{msgs}}</span>
    </div>
    // 编译之后
    
    import { mergeProps as _mergeProps } from "vue"
    import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } 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))
      }><span>hello vue3</span><span>hello vue3</span><span>hello vue3</span><span>${ // 静态节点直接输出
        _ssrInterpolate(_ctx.msgs)
      }</span></div>`)
    }

二、源码体积变小,使用Tree Shaking优化

编译时,根据不同的情况,引入不同的API,不会全部引用

  • 相比Vue2Vue3整体体积变小了,除了移出一些不常用的API,再重要的是Tree shanking
  • 任何一个函数,如refreactivecomputed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
    <!-- https://vue-next-template-explorer.netlify.app 中打开查看编译结果 -->
    <div>
      <span v-if="msg">hello vue3</span>
      <input v-model="msg" />
    </div>
    // 编译之后
    
    // 模板编译会根据模板写法 指令 插值以及用了特别的功能去动态的import相应的接口,需要什么就import什么,这就是tree shaking
    import { openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, vModelText as _vModelText, createElementVNode as _createElementVNode, withDirectives as _withDirectives } from "vue"
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, [
        (_ctx.msg)
          ? (_openBlock(), _createElementBlock("span", { key: 0 }, "hello vue3"))
          : _createCommentVNode("v-if", true),
        _withDirectives(_createElementVNode("input", {
          "onUpdate:modelValue": $event => ((_ctx.msg) = $event)
        }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [
          [_vModelText, _ctx.msg]
        ])
      ]))
    }

三、响应式系统

vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加gettersetter,实现响应式

vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历

  • 可以监听动态属性的添加
  • 可以监听到数组的索引和数组length属性
  • 可以监听删除属性

Composition API 与 Options API 有什么不同

分析

Vue3最重要更新之一就是Composition API,它具有一些列优点,其中不少是针对Options API暴露的一些问题量身打造。是Vue3推荐的写法,因此掌握好Composition API应用对掌握好Vue3至关重要

[What is Composition API? (opens new window)](https://vuejs.org/guide/extras/composition-api-faq.html#what-is- composition-api)

  • Composition API出现就是为了解决Options API导致相同功能代码分散的现象

体验

Composition API能更好的组织代码,下面用composition api可以提取为useCount(),用于组合、复用

compositon api提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

回答范例

  1. Composition API是一组API,包括:Reactivity API生命周期钩子依赖注入,使用户可以通过导入函数方式编写vue组件。而Options API则通过声明组件选项的对象形式编写组件
  2. Composition API最主要作用是能够简洁、高效复用逻辑。解决了过去Options APImixins的各种缺点;另外Composition API具有更加敏捷的代码组织能力,很多用户喜欢Options API,认为所有东西都有固定位置的选项放置代码,但是单个组件增长过大之后这反而成为限制,一个逻辑关注点分散在组件各处,形成代码碎片,维护时需要反复横跳,Composition API则可以将它们有效组织在一起。最后Composition API拥有更好的类型推断,对ts支持更友好,Options API在设计之初并未考虑类型推断因素,虽然官方为此做了很多复杂的类型体操,确保用户可以在使用Options API时获得类型推断,然而还是没办法用在mixinsprovide/inject
  3. Vue3首推Composition API,但是这会让我们在代码组织上多花点心思,因此在选择上,如果我们项目属于中低复杂度的场景,Options API仍是一个好选择。对于那些大型,高扩展,强维护的项目上,Composition API会获得更大收益

可能的追问

  1. Composition API能否和Options API一起使用?

可以在同一个组件中使用两个script标签,一个使用vue3,一个使用vue2写法,一起使用没有问题

    <!-- vue3 -->
    <script setup>
      // vue3写法
    </script>
    
    <!-- 降级vue2 -->
    <script>
      export default {
        data() {},
        methods: {}
      }
    </script>

ref如何使用

ref

  • 生成值类型的响应式数据
  • 可用于模板和reactive
  • 通过.value修改值
    <template>
        <p>ref demo {{ageRef}} {{state.name}}</p>
    </template>
    
    <script>
    import { ref, reactive } from 'vue'
    
    export default {
        name: 'Ref',
        setup() {
            const ageRef = ref(20) // 值类型 响应式
            const nameRef = ref('test')
    
            const state = reactive({
                name: nameRef
            })
    
            setTimeout(() => {
                console.log('ageRef', ageRef.value)
    
                ageRef.value = 25 // .value 修改值
                nameRef.value = 'testA'
            }, 1500);
    
            return {
                ageRef,
                state
            }
        }
    }
    </script>
    <!-- ref获取dom节点 -->
    <template>
        <p ref="elemRef">我是一行文字</p>
    </template>
    
    <script>
    import { ref, onMounted } from 'vue'
    
    export default {
        name: 'RefTemplate',
        setup() {
            const elemRef = ref(null)
    
            onMounted(() => {
                console.log('ref template', elemRef.value.innerHTML, elemRef.value)
            })
    
            return {
                elemRef
            }
        }
    }
    </script>

toRef和toRefs如何使用和最佳方式

toRef

  • 针对一个响应式对象(reactive封装的)的一个属性,创建一个ref,具有响应式
  • 两者保持引用关系

toRefs

  • 将响应式对象(reactive封装的)转化为普通对象
  • 对象的每个属性都是对象的ref
  • 两者保持引用关系

合成函数返回响应式对象

最佳使用方式

  • reactive做对象的响应式,用ref做值类型响应式(基本类型)
  • setup中返回toRefs(state),或者toRef(state, 'prop')
  • ref的变量命名都用xxRef
  • 合成函数返回响应式对象时,使用toRefs,有助于使用方对数据进行解构时,不丢失响应式
    <template>
        <p>toRef demo - {{ageRef}} - {{state.name}} {{state.age}}</p>
    </template>
    
    <script>
    import { ref, toRef, reactive } from 'vue'
    
    export default {
        name: 'ToRef',
        setup() {
            const state = reactive({
                age: 20,
                name: 'test'
            })
    
            const age1 = computed(() => {
                return state.age + 1
            })
    
            // toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式
            // const state = {
            //     age: 20,
            //     name: 'test'
            // }
            // 一个响应式对象state其中一个属性要单独拿出来实现响应式用toRef
            const ageRef = toRef(state, 'age')
    
            setTimeout(() => {
                state.age = 25
            }, 1500)
    
            setTimeout(() => {
                ageRef.value = 30 // .value 修改值
            }, 3000)
    
            return {
                state,
                ageRef
            }
        }
    }
    </script>
    <template>
        <p>toRefs demo {{age}} {{name}}</p>
    </template>
    
    <script>
    import { ref, toRef, toRefs, reactive } from 'vue'
    
    export default {
        name: 'ToRefs',
        setup() {
            const state = reactive({
                age: 20,
                name: 'test'
            })
    
            const stateAsRefs = toRefs(state) // 将响应式对象,变成普通对象
    
            // const { age: ageRef, name: nameRef } = stateAsRefs // 每个属性,都是 ref 对象
            // return {
            //     ageRef,
            //     nameRef
            // }
    
            setTimeout(() => {
                state.age = 25
            }, 1500)
    
            return stateAsRefs
        }
    }
    </script>

深入理解为什么需要ref、toRef、toRefs

为什么需要用 ref

  • 返回值类型,会丢失响应式
  • 如在setupcomputed、合成函数,都有可能返回值类型
  • Vue如不定义ref,用户将制造ref,反而更混乱

为何ref需要.value属性

  • ref是一个对象(不丢失响应式),value存储值
  • 通过.value属性的getset实现响应式
  • 用于模板、reactive时,不需要.value,其他情况都要

为什么需要toRef和toRefs

  • 初衷 :不丢失响应式的情况下,把对象数据 分解/扩散
  • 前端 :针对的是响应式对象(reactive封装的)非普通对象
  • 注意:不创造 响应式,而是延续 响应式
    <template>
        <p>why ref demo {{state.age}} - {{age1}}</p>
    </template>
    
    <script>
    import { ref, toRef, toRefs, reactive, computed } from 'vue'
    
    function useFeatureX() {
        const state = reactive({
            x: 1,
            y: 2
        })
    
        return toRefs(state)
    }
    
    export default {
        name: 'WhyRef',
        setup() {
            // 解构不丢失响应式
            const { x, y } = useFeatureX()
    
            const state = reactive({
                age: 20,
                name: 'test'
            })
    
            // computed 返回的是一个类似于 ref 的对象,也有 .value
            const age1 = computed(() => {
                return state.age + 1
            })
    
            setTimeout(() => {
                state.age = 25
            }, 1500)
    
            return {
                state,
                age1,
                x,
                y
            }
        }
    }
    </script>

ref和reactive异同

这是Vue3数据响应式中非常重要的两个概念,跟我们写代码关系也很大

    const count = ref(0)
    console.log(count.value) // 0

    count.value++
    console.log(count.value) // 1
    
    const obj = reactive({ count: 0 })
    obj.count++
  • ref接收内部值(inner value)返回响应式Ref对象,reactive返回响应式代理对象
  • 从定义上看ref通常用于处理单值的响应式,reactive用于处理对象类型的数据响应式
  • 两者均是用于构造响应式数据,但是ref主要解决原始值的响应式问题
  • ref返回的响应式数据在JS中使用需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.valueref可以接收对象或数组等非原始值,但内部依然是reactive实现响应式;reactive内部如果接收Ref对象会自动脱ref;使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开。
  • reactive内部使用Proxy代理传入对象并拦截该对象各种操作,从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式

vue3升级了哪些重要功能

1. createApp

    // vue2
    const app = new Vue({/**选项**/})
    Vue.use(/****/)
    Vue.mixin(/****/)
    Vue.component(/****/)
    Vue.directive(/****/)
    
    // vue3
    const app = createApp({/**选项**/})
    app.use(/****/)
    app.mixin(/****/)
    app.component(/****/)
    app.directive(/****/)

2. emits属性

    // 父组件
    <Hello :msg="msg" @onSayHello="sayHello">
    
    // 子组件
    export default {
        name: 'Hello',
        props: {
            msg: String
        },
        emits: ['onSayHello'], // 声明emits
        setup(props, {emit}) {
            emit('onSayHello', 'aaa')
        }
    }

3. 多事件

    <!-- 定义多个事件 -->
    <button @click="one($event),two($event)">提交</button>

4. Fragment

    <!-- vue2 -->
    <template>
        <div>
            <h2>{{title}}</h2>
            <p>test</p>
        </div>
    </template>
    
    <!-- vue3:不在使用div节点包裹 -->
    <template>
        <h2>{{title}}</h2>
        <p>test</p>
    </template>

5. 移除.sync

    <!-- vue2 -->
    <MyComponent :title.sync="title" />
    
    <!-- vue3 简写 -->
    <MyComponent v-model:title="title" />
    <!-- 非简写 -->
    <MyComponent :title="title" @update:title="title = $event" />

.sync用法

父组件把属性给子组件,子组件修改了后还能同步到父组件中来

    <template>
      <button @click="close">关闭</button>
    </template>
    <script>
    export default {
        props: {
            isVisible: {
                type: Boolean,
                default: false
            }
        },
        methods: {
            close () {
                this.$emit('update:isVisible', false);
            }
        }
    };
    </script>
    <!-- 父组件使用 -->
    <chlid-component :isVisible.sync="isVisible"></chlid-component>
    <text-doc :title="doc.title" @update:title="doc.title = $event"></text-doc>
    
    <!-- 为了方便期间,为这种模式提供一个简写 .sync -->
    <text-doc :title.sync="doc.title" />

6. 异步组件的写法

    // vue2写法
    new Vue({
        components: {
            'my-component': ()=>import('./my-component.vue')
        }
    })
    // vue3写法
    import {createApp, defineAsyncComponent} from 'vue'
    
    export default {
        components: {
            AsyncComponent: defineAsyncComponent(()=>import('./AsyncComponent.vue'))
        }
    }

7. 移除filter

    <!-- 以下filter在vue3中不可用了 -->
    
    <!-- 在花括号中 -->
    {{message | capitalize}}
    
    <!-- 在v-bind中 -->
    <div v-bind:id="rawId | formatId"></div>

8. Teleport

    <button @click="modalOpen = true">
     open
    </button>
    
    <!-- 通过teleport把弹窗放到body下 -->
    <teleport to="body">
     <div v-if="modalOpen" classs="modal">
       <div>
         teleport弹窗,父元素是body
         <button @click="modalOpen = false">close</button>
       </div>
     </div>
    </teleport>

9. Suspense

    <Suspense>
     <template>
        <!-- 异步组件 -->
       <Test1 />  
     </template>
     <!-- fallback是一个具名插槽,即Suspense内部有两个slot,一个具名插槽fallback -->
     <template #fallback>
        loading...
     </template>
    </Suspense>

10. Composition API

  • reactive
  • ref
  • readonly
  • watchwatchEffect
  • setup
  • 生命周期钩子函数

Vue3.2 setup 语法糖汇总

提示:vue3.2 版本开始才能使用语法糖!

Vue3.0 中变量必须 return 出来, template 中才能使用;而在 Vue3.2 中只需要在 script 标签上加上 setup 属性,无需 returntemplate 便可直接使用,非常的香啊!

1. 如何使用setup语法糖

只需在 script 标签上写上 setup

    <template>
    </template>
    <script setup>
    </script>
    <style scoped lang="less">
    </style>

2. data数据的使用

由于 setup 不需写 return ,所以直接声明数据即可

    <script setup>
    import {
      ref,
      reactive,
      toRefs,
    } from 'vue'
    
    const data = reactive({
      patternVisible: false,
      debugVisible: false,
      aboutExeVisible: false,
    })
    
    const content = ref('content')
    //使用toRefs解构
    const { patternVisible, debugVisible, aboutExeVisible } = toRefs(data)
    </script>

3. method方法的使用

    <template >
      <button @click="onClickHelp">帮助</button>
    </template>
    <script setup>
    import {reactive} from 'vue'
    
    const data = reactive({
      aboutExeVisible: false,
    })
    // 点击帮助
    const onClickHelp = () => {
      console.log(`帮助`)
      data.aboutExeVisible = true
    }
    </script>

4. watchEffect的使用

    <script setup>
    import {
      ref,
      watchEffect,
    } from 'vue'
    
    let sum = ref(0)
    
    watchEffect(()=>{
      const x1 = sum.value
      console.log('watchEffect所指定的回调执行了')
    })
    </script>

5. watch的使用

    <script setup>
    import {
      reactive,
      watch,
    } from 'vue'
    //数据
    let sum = ref(0)
    let msg = ref('hello')
    let person = reactive({
      name:'张三',
      age:18,
      job:{
        j1:{
          salary:20
        }
      }
    })
    // 两种监听格式
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变了',newValue,oldValue)
      },
      {immediate:true}
    )
    
    watch(()=>person.job,(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) 
    
    </script>

6. computed计算属性的使用

computed 计算属性有两种写法(简写和考虑读写的完整写法)

    <script setup>
    import {
      reactive,
      computed,
    } from 'vue'
    
    // 数据
    let person = reactive({
      firstName:'poetry',
      lastName:'x'
    })
    
    // 计算属性简写
    person.fullName = computed(()=>{
      return person.firstName + '-' + person.lastName
    })
    
    // 完整写法
    person.fullName = computed({
      get(){
        return person.firstName + '-' + person.lastName
      },
      set(value){
        const nameArr = value.split('-')
        person.firstName = nameArr[0]
        person.lastName = nameArr[1]
      }
    })
    </script>

7. props父子传值的使用

父组件代码如下(示例):

    <template>
      <child :name='name'/>  
    </template>
    
    <script setup>
      import {ref} from 'vue'
      // 引入子组件
      import child from './child.vue'
      let name= ref('poetry')
    </script>

子组件代码如下(示例):

    <template>
      <span>{{props.name}}</span>
    </template>
    
    <script setup>
    import { defineProps } from 'vue'
    // 声明props
    const props = defineProps({
      name: {
        type: String,
        default: 'poetries'
      }
    })  
    // 或者
    //const props = defineProps(['name'])
    </script>

8. emit子父传值的使用

父组件代码如下(示例):

    <template>
      <AdoutExe @aboutExeVisible="aboutExeHandleCancel" />
    </template>
    <script setup>
    import { reactive } from 'vue'
    // 导入子组件
    import AdoutExe from '../components/AdoutExeCom'
    
    const data = reactive({
      aboutExeVisible: false, 
    })
    // content组件ref
    
    // 关于系统隐藏
    const aboutExeHandleCancel = () => {
      data.aboutExeVisible = false
    }
    </script>

子组件代码如下(示例):

    <template>
      <a-button @click="isOk">
        确定
      </a-button>
    </template>
    <script setup>
    import { defineEmits } from 'vue';
    
    // emit
    const emit = defineEmits(['aboutExeVisible'])
    /**
     * 方法
     */
    // 点击确定按钮
    const isOk = () => {
      emit('aboutExeVisible');
    }
    </script>

9. 获取子组件ref变量和defineExpose暴露

vue2中的获取子组件的ref,直接在父组件中控制子组件方法和变量的方法

父组件代码如下(示例):

    <template>
      <button @click="onClickSetUp">点击</button>
      <Content ref="content" />
    </template>
    
    <script setup>
    import {ref} from 'vue'
    
    // content组件ref
    const content = ref('content')
    // 点击设置
    const onClickSetUp = ({ key }) => {
      content.value.modelVisible = true
    }
    </script>
    <style scoped lang="less">
    </style>

子组件代码如下(示例):

    <template>
       <p>{{data }}</p>
    </template>
    
    <script setup>
    import {
      reactive,
      toRefs
    } from 'vue'
    
    /**
     * 数据部分
    * */
    const data = reactive({
      modelVisible: false,
      historyVisible: false, 
      reportVisible: false, 
    })
    
    defineExpose({
      ...toRefs(data),
    })
    </script>

10. 路由useRoute和useRouter的使用

    <script setup>
      import { useRoute, useRouter } from 'vue-router'
    
      // 声明
      const route = useRoute()
      const router = useRouter()
    
      // 获取query
      console.log(route.query)
      // 获取params
      console.log(route.params)
    
      // 路由跳转
      router.push({
        path: `/index`
      })
    </script>

11. store仓库的使用

    <script setup>
      import { useStore } from 'vuex'
      import { num } from '../store/index'
    
      const store = useStore(num)
    
      // 获取Vuex的state
      console.log(store.state.number)
      // 获取Vuex的getters
      console.log(store.state.getNumber)
    
      // 提交mutations
      store.commit('fnName')
    
      // 分发actions的方法
      store.dispatch('fnName')
    </script>

12. await的支持

setup语法糖中可直接使用await,不需要写asyncsetup会自动变成async setup

    <script setup>
      import api from '../api/Api'
      const data = await Api.getData()
      console.log(data)
    </script>

13. provide 和 inject 祖孙传值

父组件代码如下(示例):

    <template>
      <AdoutExe />
    </template>
    
    <script setup>
      import { ref,provide } from 'vue'
      import AdoutExe from '@/components/AdoutExeCom'
    
      let name = ref('py')
      // 使用provide
      provide('provideState', {
        name,
        changeName: () => {
          name.value = 'poetries'
        }
      })
    </script>

子组件代码如下(示例):

    <script setup>
      import { inject } from 'vue'
      const provideState = inject('provideState')
    
      provideState.changeName()
    </script>

v-model参数的用法

vue2自定义组件model

    <!-- 自定义 v-model -->
    <CustomVModel v-model="name"/> 
    <!-- CustomVModel -->
    <template>
      <!-- 例如:vue 颜色选择 -->
      <input type="text"
        :value="text"
        @input="$emit('change', $event.target.value)"
      >
      <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change 和 model.event 要对应起来
        3. text 属性对应起来
      -->
    </template>
    
    <script>
    export default {
        model: {
            prop: 'text', // 对应 props text
            event: 'change'
        },
        props: {
            text1: String,
            default() {
                return ''
            }
        }
    }
    </script>

vue3自定义组件model

    <!-- index.vue 绑定一个值 -->
    <CustomVModel v-model="name"/> 
    
    <!-- CustomVModel.vue -->
    <template>
        <input type="text"
            :value="modelValue"
            @input="$emit('update:modelValue', $event.target.value)"
        >
    </template>
    
    <script>
    export default {
        props: {
            modelValue: String,
        }
    }
    </script>
    <!-- UserInfo组件 -->
    <template>
      <input :value="name" @input="$emit('update:name', $event.target.value)"/>
      <input :value="age" @input="$emit('update:age', $event.target.value)"/>
    </template>
    
    <script>
    export default {
      name: 'UserInfo',
      props: {
        name: String,
        age: String
      }
    }
    </script>
    <!-- 绑定多个值
    使用 vue3中v-model可绑定多个属性 -->
    <user-info
        v-model:name="name"
        v-model:age="age"
    ></user-info>

watch和watchEffect的区别

  • 两者都可以监听data属性变化
  • watch需要明确监听哪个属性
  • watchEffect会根据其中的属性,自动监听其变化
    <template>
        <p>watch vs watchEffect</p>
        <p>{{numberRef}}</p>
        <p>{{name}} {{age}}</p>
    </template>
    
    <script>
    import { reactive, ref, toRefs, watch, watchEffect } from 'vue'
    
    export default {
        name: 'Watch',
        setup() {
            const numberRef = ref(100)
            const state = reactive({
                name: 'test',
                age: 20
            })
    
            watchEffect(() => {
                // 初始化时,一定会执行一次(收集要监听的数据)
                console.log('hello watchEffect')
            })
            watchEffect(() => {
                console.log('state.name', state.name)
            })
            watchEffect(() => {
                console.log('state.age', state.age)
            })
            watchEffect(() => {
                console.log('state.age', state.age)
                console.log('state.name', state.name)
            })
            setTimeout(() => {
                state.age = 25
            }, 1500)
            setTimeout(() => {
                state.name = 'testA'
            }, 3000)
            
    
            // ref直接写
            // watch(numberRef, (newNumber, oldNumber) => {
            //     console.log('ref watch', newNumber, oldNumber)
            // }
            // // , {
            // //     immediate: true // 初始化之前就监听,可选
            // // }
            // )
    
            // setTimeout(() => {
            //     numberRef.value = 200
            // }, 1500)
    
            // watch(
            //     // 第一个参数,确定要监听哪个属性
            //     () => state.age,
    
            //     // 第二个参数,回调函数
            //     (newAge, oldAge) => {
            //         console.log('state watch', newAge, oldAge)
            //     },
    
            //     // 第三个参数,配置项
            //     {
            //         immediate: true, // 初始化之前就监听,可选
            //         // deep: true // 深度监听
            //     }
            // )
    
            // setTimeout(() => {
            //     state.age = 25
            // }, 1500)
            // setTimeout(() => {
            //     state.name = 'zhangsanA'
            // }, 3000)
    
            return {
                numberRef,
                ...toRefs(state)
            }
        }
    }
    </script>

setup中如何获取组件实例

  • setup和其他composition API中没有this
  • 通过getCurrentInstance获取当前实例
  • 若使用options API可以照常使用this
    import { onMounted, getCurrentInstance } from 'vue'
    
    export default {
        name: 'GetInstance',
        data() {
            return {
                x: 1,
                y: 2
            }
        }, 
        setup() { // setup是created beforeCreate 合集 组件还没正式初始化
            console.log('this1', this) // undefined
    
            onMounted(() => {
                console.log('this in onMounted', this) // undefined
                console.log('x', instance.data.x) // 1  onMounted中组件已经初始化了
            })
     
            const instance = getCurrentInstance()
            console.log('instance', instance)
        },
        mounted() {
            console.log('this2', this)
            console.log('y', this.y)
        }
    }

Vite 为什么启动非常快

  • 开发环境使用Es6 Module,无需打包,非常快
  • 生产环境使用rollup,并不会快很多

ES Module 在浏览器中的应用

    <p>基本演示</p>
    <script type="module">
        import add from './src/add.js'
    
        const res = add(1, 2)
        console.log('add res', res)
    </script>
    <script type="module">
        import { add, multi } from './src/math.js'
        console.log('add res', add(10, 20))
        console.log('multi res', multi(10, 20))
    </script>
    <p>外链引用</p>
    <script type="module" src="./src/index.js"></script>
    <p>远程引用</p>
    <script type="module">
        import { createStore } from 'https://unpkg.com/redux@latest/es/redux.mjs' // es module规范mjs
        console.log('createStore', createStore)
    </script>
    <p>动态引入</p>
    <button id="btn1">load1</button>
    <button id="btn2">load2</button>
    
    <script type="module">
        document.getElementById('btn1').addEventListener('click', async () => {
            const add = await import('./src/add.js')
            const res = add.default(1, 2)
            console.log('add res', res)
        })
        document.getElementById('btn2').addEventListener('click', async () => {
            const { add, multi } = await import('./src/math.js')
            console.log('add res', add(10, 20))
            console.log('multi res', multi(10, 20))
        })
    </script>

说说Vue 3.0中Tree shaking特性?举例说明一下?

一、是什么

Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫 Dead code elimination

简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码

如果把代码打包比作制作蛋糕,传统的方式是把鸡蛋(带壳)全部丢进去搅拌,然后放入烤箱,最后把(没有用的)蛋壳全部挑选并剔除出去

treeshaking则是一开始就把有用的蛋白蛋黄(import)放入搅拌,最后直接作出蛋糕

也就是说 ,tree shaking 其实是找出使用的代码

Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到

    import Vue from 'vue'
     
    Vue.nextTick(() => {})

Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中

    import { nextTick, observable } from 'vue'
     
    nextTick(() => {})

二、如何做

Tree shaking是基于ES6模板语法(importexports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量

Tree shaking无非就是做了两件事:

  • 编译阶段利用ES6 Module判断哪些模块已经加载
  • 判断那些模块和变量未被使用或者引用,进而删除对应代码

下面就来举个例子:

通过脚手架vue-cli安装Vue2Vue3项目

    vue create vue-demo

Vue2 项目

组件中使用data属性

    <script>
        export default {
            data: () => ({
                count: 1,
            }),
        };
    </script>

对项目进行打包,体积如下图

为组件设置其他属性(comptedwatch

    export default {
        data: () => ({
            question:"", 
            count: 1,
        }),
        computed: {
            double: function () {
                return this.count * 2;
            },
        },
        watch: {
            question: function (newQuestion, oldQuestion) {
                this.answer = 'xxxx'
            }
    };

再一次打包,发现打包出来的体积并没有变化

Vue3 项目

组件中简单使用

    import { reactive, defineComponent } from "vue";
    export default defineComponent({
      setup() {
        const state = reactive({
          count: 1,
        });
        return {
          state,
        };
      },
    });

将项目进行打包

在组件中引入computedwatch

    import { reactive, defineComponent, computed, watch } from "vue";
    export default defineComponent({
      setup() {
        const state = reactive({
          count: 1,
        });
        const double = computed(() => {
          return state.count * 2;
        });
    
        watch(
          () => state.count,
          (count, preCount) => {
            console.log(count);
            console.log(preCount);
          }
        );
        return {
          state,
          double,
        };
      },
    });

再次对项目进行打包,可以看到在引入computerwatch之后,项目整体体积变大了

三、作用

通过Tree shakingVue3给我们带来的好处是:

  • 减少程序体积(更小)
  • 减少程序执行时间(更快)
  • 便于将来对程序架构进行优化(更友好)
Last Updated:
Contributors: leeguooooo