• Edwin's Blog
  • 首页
  • 文章
  • 分类
  • 项目
  • 关于

Vue Basic Knowledge Points

Vue

Vue Basic Knowledge Points

2025-10-21

vue

v-if 和 v-show 的区别

  • v-if , 当它们的值是 false 时,元素被移除掉
  • v-show,当它的值是 false 时,元素只是通过 display:none 来隐藏元素
  • v-if 本质就是 创建和销毁元素
  • v-show 是通过 display:none css样式的方式来隐藏元素
  • v-for 和 v-if 的优先级:
  • v-for 的优先级更高,如果同时出现在同级每次渲染都会先执行循环再判断条件,由此可见对性能损耗比较大,可使用计算属性先筛选需要的数据

v-model 原理

v-bind:value

v-on:事件(input)  手动取值(e.target.value),并赋值

存在问题:v-model 和 value 有一个强绑定关系,如果子组件中有一个 input 原生标签,此时就会影响原生标签的 value 和 input 事件,想要支持多属性的话,需要使用.sync
如果需要修改默认的 prop 或 event,需通过组件的 model 选项配置

export default {
  model: {
    prop: 'checked', // 改为用checked接收值
    event: 'change'  // 改为用change事件传递更新
  },
  props: ['checked']
};

vue3 统一 使用 v-model 进行多个数据双向绑定,废除了 model 组件选项,使用 modelValue 和 update:modelValue, 并支持多 v-model 绑定(替代 Vue2 的 .sync)

计算属性、监听器及其二者区别

计算属性 computed

  作用:对多个声明式变量进行复杂运算,以减少在指令使用复杂的表达式
  特点:依赖于Vue的响应式系统,自己关联的声明式变量只要不发生变化,不会重新计算,具有缓存作用
  原理:getter、setter 钩子函数

侦听器 watch

  作用:用于监听一个变量的变化,可以监听哪些变量呢?

    声明式变量
    计算属性
    路由$route

区别:

  watch 可以进行异步操作,computed 不行
  computed 依赖于Vue的响应式系统,具有缓存作用,watch没有

计算属性默认只有 getter属性,如果需要 setter 属性,改成对象的写法

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

watch 如果需要配置其他属性(深度监听),需要改成 对象的形式

watch: {
  myName(newValue,oldValue){
  // do someing
  },
  myNumber: {
    handler(newVal, oldVal) {
      console.log('newVal', newVal);
      console.log('oldVal', oldVal);
    },
    //immediate为true时则立即触发回调函数;如果为false,则和上面的例子一样,不会立即执行回调。
    immediate: true,
    deep:true
  }
}

methods 与 computed 区别

  • 调用方式不同。computed直接以对象属性方式调用,不需要加括号,而 methods 必须要函数执行才可以得到结果
  • 绑定方式不同。methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定
  • 是否存在缓存。methods没有缓存,调用相同的值计算还是会重新计算。competed有缓存,在值不变的情况下不会再次计算,而是直接使用缓存中的值

$nextTick

  • 在下次 DOM 更新循环结束之后,执行延迟回调,获取更新后的 DOM
  • 视图还没更新完,获取不到 dom 节点的信息
  • 原理:Vue的异步更新策略,就是如果数据发生变化,Vue不会立刻更新 DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新
  • 使用场景
    • created中获取DOM时
    • 响应式数据变化后获取DOM更新的状态,比如 列表更新后的高度
    • nextTick 有一个参数 ,是回调函数,作用是,等待 视图更新后,再执行 回调函数中的代码

Vue 组件传参

  • 父子组件通信
    • 父传子通过props
    • 子传父通过自定义事件
  • 兄弟组件通信
    • 事件总线 eventbus 是基于一个消息中心,订阅和发布信息的模式 $on $emit
  • vuex
  • 跨组件通信
    • provide / inject
  • 可以通过 $parent 直接获取到父组件的实例,可以通过 $children 直接获取到子组件的实例
  • ref

vue 的 router 和 route 区别是什么

  • router 是 VueRouter 的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
    • $router.push
    • $router.replace
  • route 是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
    • $route.path
    • $route.params
    • $route.name
    • $route.query:含路由中查询参数的键值对

Vue 路由守卫

  • 全局守卫(3):在所有路由导航之前或之后执行
    • 全局前置守卫 beforeEach((to,from,next)=>{})
    • 全局解析守卫 beforeResolve ((to,from,next)=>{})
    • 全局后置钩子 afterEach((to,from)=>{}) 没有 next()
  • 路由独享守卫(1): 在进入特定路由之前执行
    • beforeEnter:(to,from)=>{}
  • 组件内守卫(3):在进入或离开特定组件时执行
    • beforeRouteEnter
      • 在渲染该组件的对应路由被验证前调用
      • 不能获取组件实例 this
      • 因为当守卫执行时,组件实例还没被创建!
    • beforeRouteUpdate (可以访问 this)
    • beforeRouteLeave

为什么 data 属性是一个函数而不是一个对象?

  • 形成一份独立的作用域,不会受到其他实例对象数据的污染,避免变量全局污染

Vue 的生命周期

  • Vue的生命周期,总共有11种,常用的有8种,分为4个阶段,分别是:创建、挂载、更新、销毁阶段,
  • 开始先实例化,初始化事件及钩子函数,
  • 然后进入第一个阶段,创建阶段,beforeCreate ----> created ,响应式原理就发生在这个阶段,具体是通过对Data选项进行遍历,使用object.defineProperty进行响应式数据劫持,并把这些 转化为 getter/setter, 把劫持到的数据赋值到当前实例化对象上,当data中的数据发生变化时触发 getter/ setter(钩子函数),然后通知 watcher,通过 watcher 进行视图更新,视图发生更新进而生成新的虚拟DOM,,可以在创建完成时期(created),进行发请求,传数据,调接口,
  • 创建阶段结束之后,就开始找视图结构,找到视图结构之后,
  • 就进入了第二个阶段( beforeMount -----> mounted)挂载阶段,在这个阶段,创建虚拟DOM对象,然后把虚拟DOM对象替换成真实的数据,完成视图的渲染,挂载完成之后,可以进行调接口,开启定时器,长连接,DOM操作等。
  • 当声明式变量发生变化时,就进入第三阶段( beforeUpdate ----> updated )更新阶段,再创建一个新的虚拟DOM,然后运用 diff算法,把新的和旧的虚拟DOM进行比较,找出两者之间变化的最小差异,标记为脏节点,然后 派发(patch) 给 Watcher,通过 Watcher 转化为真实的DOM,完成页面的渲染,只要声明式变量再次发生改变时,就会一直循环。
  • 最后一个为销毁阶段(beforeDestroy ---> destroyed),在销毁之前,可以关闭定时器,关闭长连接,清除耗费内存的其他变量,最后销毁
  • 动态组件 keep-alive
    • 它是Vue内置组件,在Vue系统中可以直接使用
    • 作用:被keep-alive所包裹的组件,不会“死”,不会被销毁
    • 生命周期:被keep-alive包裹的组件,有两个特殊的生命周期
      • activated:当组件被激活时触发
      • deactivated:当组件被停用时触发
      • keep-alive 有三个属性 :
        • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
        • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
        • max - 数字最多可以缓存多少组件实例

父子组件生命周期执行顺序

  • 加载渲染过程
    • 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子beforeMount -> 子 Mounted -> 父 Mounted
  • 更新过程
    • 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
  • 销毁阶段
    • 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

Vuex 工作流程

  • Vuex在工作中,主要用于解决组件之间数据共享的问题,当我们需要定义共享数据的时候,定义在状态管理的State中,页面中如果发生各种交互行为,需要修改state,我们可以通过Actions或者Mutations方法,当我们需要通过后端调接口时,我们就封装actions方法,在页面(组件)的 Mouted 生命周期或者Created 生命周期中,进行派发并触发(Dispatch)一个 action 方法,并把参数传递过去,得到后端异步数据后,我们在状态管理的Actions中commit(提交并触发)Mutations方法,来修改state,在页面中我们使用...mapState来使用数据,每次state更新,页面自动更新,如果页面中没有涉及到页面数据,只是同步数据,我们直接在页面事件处理器中,通过commit(提交并触发)Mutations方法,来修改state,state发生变化,页面自动更新

Vuex 数据持久化原理

  • 为什么需要持久化?
    • Vuex 的状态(state)默认存储在 内存 中,而内存中的数据会在以下场景被清空:
    • 页面刷新(F5 或浏览器刷新)
    • 浏览器标签页关闭
    • 浏览器重启
  • Vuex 数据持久化的实现依赖两个关键步骤:“保存” 和 “恢复”,结合浏览器的本地存储完成状态的持久化循环
    • 状态的 “保存”,当 Vuex 中的状态发生变化时(通常是通过 mutation 修改),将最新的状态序列化后存储到本地存储(如 localStorage)中。
      • 序列化:由于本地存储只能存储字符串,需通过 JSON.stringify() 将状态对象转为字符串。
      • 触发时机:通常在每次 mutation 执行后触发保存(确保状态变更后立即持久化)。
    • 状态的 “恢复”,在 Vuex 初始化(store 创建)时,从本地存储中读取之前保存的状态字符串,反序列化为对象后,合并到 Vuex 的初始状态中。
      • 反序列化:通过 JSON.parse() 将本地存储的字符串转为对象。
      • 合并策略:通常将本地存储的状态与默认初始状态合并(避免覆盖未持久化的状态)
  • 持久化原理Vuex 数据持久化的本质是:利用浏览器本地存储(如 localStorage)作为 “中间介质”,在状态变更时保存,在应用初始化时恢复,从而突破内存存储的临时性限制
  • vuex只是在内存保存状态,刷新之后就会丢失
  • 使用插件(vue-persist、vue-persistedstate) 内部实现就是通过订阅(subscriber) mutation变化做统一处理,通过插件的选项控制哪些需要持久化
  • 提交 mutation的时候同时存入 localStorage,store中把值取出来 作为state的初始值即可
  • subscriber 方法

Vue.use() 做了什么工作?

  • Vue.use() 是全局注册一个组件或者插件的方法。每次注册前,都会判断一下这个组件或者插件(plugins)是否注册过,如果注册过,就不会再次注册了。
  • 判断这个插件是否被注册过,如果已经注册了,不允许重复注册。如果插件没有被注册过,那么注册成功之后会给插件添加一个 installed 的属性,其值为true。Vue.use方法内部会检测插件的installed属性,从而避免重复注册插件.
  • 接收的plugin参数的限制是 Object | Function 两种类型之一
  • 如果是对象
    • 该对象里面要有一个 install 方法
    • Vue.use就是调用里面的 install 方法 ,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象,用于传入插件的配置
  • 如果是一个 function
    • Vue.use时 function 会直接执行
    • 作用
      • 添加全局方法或者属性。如: vue-custom-element
      • 添加全局资源:指令/过滤器/过渡/组件等。如 vue-touch
      • 通过全局混入来添加一些组件选项。如 vue-router
      • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现
      • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

Vue 依赖收集

通过 Object.definedproperty,进行响应式数据劫持,来设置观察属性的 setter 和 getter

通过 getter 收集依赖,通过 setter 触发依赖更新

get事件在属性没有变化时触发并且还会触发dep收集依赖

set事件在属性发生变化触发并且触发dep收集依赖再触发Watch执行更新

跨域与浏览器同源策略

  • 什么是跨域?
    • "协议://域名:端口",有任何一个不同就是跨域
  • 什么是 浏览器同源策略?
    • 浏览器同源策略,是一种安全机制,它的特点是,阻止Ajax进行跨域(非同源)下的数据请求
    • 同源策略只在浏览器中才起作用,在Node服务器上使用ajax工具跨域请求,是没有任何问题
  • 总结:只有在浏览器中,同源策略才起阻止ajax的跨域请求
  • 如何解决跨域请求的问题?(常用有三种)
    • JSONP 原理:利用 script 的 src属性,浏览器不会阻止, 只能解决GET请求的跨域问题
    • CORS 在后端添加 headers 以允许被同源访问(后端配置允许跨域的源)
    • 代理 前端代理、后端 Nginx/Apache代理
  • 前端代理解决 "跨域请求" 的机制是怎样的呢?
    • 让前端业务调接口,访问本地服务器(localhost:8080),如此就不跨域
    • 本地服务器做了代理,当收到前端业务请求时,进行代理转发,相当于是node服务向远程服务器发送请求
    • node服务向远程发送请求时,跨域了,但是node环境中没有CORS同源策略
  • 在本地开发环境下,可以使用前端代理,当上线后可以使用后端代理

Vue 的 diff 算法

  • 比较只会在同层级进行, 不会跨层级比较
  • 在diff比较的过程中,循环从两边向中间比较

diff 算法比较方式

  • diff整体策略为:深度优先,同层比较
  • 比较只会在同层级进行, 不会跨层级比较
  • 比较的过程中,循环从两边向中间收拢

Diff 运算的工作流程:(发生在更新阶段)

  • 在挂载阶段,生成一个虚拟DOM,保存在内存
  • 在声明式数据发生变化时(更新阶段),Vue会生成一个新的虚拟DO
  • 使用diff(vm1,vm2),找出两个虚拟DOM之间变化最小差异,将其标记为脏节
  • 接着把脏节点patch到Watcher,使用真实DOM操作api更新视图

如何理解虚拟 Dom

  • 虚拟DOM初始生成,发在挂载阶段beforeMoute--->Mouted
  • 是一个json对象,是根据真实Dom结构生成的,用于描述真实DOM结构,保存在内存中
  • 虚拟DOM的作用:给DOM更新提供了中间层,避免用户过渡地操作真实DOM,提高web性能
  • Vue性能高原因:Vue工作每次都会生成一个新的虚拟DOM对象,然后使用diff运算,对比新的和旧的虚拟DOM,找出他们之间的变化的最小差异,再通过真实的DOM操作,把最小差异更新到页面上

Vue3.0 新特性

在 setup() 函数中手动暴露大量的状态和方法非常繁琐,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码

DOM 更新时机​

  当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
  
  要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API

reactive() API 有一些局限性:

  有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型
  
  不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失
  
  对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接

    // 1.使用 toRefs() 解构整个对象
    import { reactive, toRefs } from 'vue';
    const state = reactive({ count: 0, name: 'Vue3' });
    const { count, name } = toRefs(state); // ✅ 保持响应式
    // 修改时需用 .value
    count.value = 10;

    // 2.toRef()解构单个属性
    ...
    const count = toRef(state, 'count'); // ✅ 保持响应式

透传 Attributes
  $attrs 属性来指定接收的元素
  
  // 父组件传递 class 
  <MyComponent class="baz" />
  
  // 子组件存在多个根节点时
  <p :class="$attrs.class">Hi!</p>
  <span>This is a child component</span> 

v-model本质上是 props 和 emit 的语法糖

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新
  push()
  pop()
  shift()
  unshift()
  splice()
  sort()
  reverse()

不可变 (immutable) 方法
  concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的
  // `items` 是一个数组的 ref
  items.value = items.value.filter((item) => item.message.match(/Foo/))

在计算属性中使用 reverse() 和 sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本
  - return numbers.reverse()
  + return [...numbers].reverse()

事件修饰符
  .stop
  .prevent
  .self
  .capture
  .once
  .passive

按键修饰符
  <!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
  <input @keyup.enter="submit" />

表单修饰符
  .lazy
  .number
  .trim

watch

  第一个参数可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组

  不能直接侦听响应式对象的属性值,需要用一个返回该属性的 getter 函数

  深层侦听:直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发

  watch 默认是懒执行的:仅当数据源变化时,才会执行回调,通过传入 { immediate: true } 选项来强制侦听器的回调立即执行

watchEffect() 

  允许我们自动跟踪回调的响应式依赖,回调会立即执行

  watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪

watch 和 watchEffect 都能响应式地执行有副作用的回调,它们之间的主要区别是追踪响应式依赖的方式:
  
  watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机
    
  watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确

访问模板引用

  要在组合式 API 中获取引用,我们可以使用辅助函数 useTemplateRef(),只能在组件挂载后才能访问模板引用

props

  要在 setup 中 接受 props,可以使用 defineProps

  <script setup>
    const props = defineProps(['foo']) 
    console.log(props.foo)
  </script>

当我们需要传递解构的 prop 到外部函数中并保持响应性时

   useComposable(() => foo)

外部函数可以调用 getter (或使用 toValue 进行规范化) 来追踪提供的 prop 变更。例如,在计算属性或侦听器的 getter 中

插槽
  具名插槽: 带 name 的插槽
  作用域插槽: 想要同时使用父组件域内和子组件域内的数据

provide / inject
  provide ('key名','value值')
  const xxx = inject('key名')

异步组件
  使用 defineAsyncComponent
  import { defineAsyncComponent } from 'vue'
  const AsyncComp = defineAsyncComponent(() =>
    import('./components/MyComponent.vue')
  )

内置组件:

  <Suspense>
    可以等待的异步依赖有两种
      1.带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件
      2.异步组件
    三种事件:pending、resolve 和 fallback。pending 事件是在进入挂起状态时触发。resolve 事件是在 default 插槽完成获取新内容时触发。fallback 事件则是在 fallback 插槽的内容显示时触发

  <Transition>
    会在一个元素或组件进入和离开 DOM 时应用动画
    触发进入/离开时(如 v-if、v-show 变化)类名,vue会自动添加,但是样式需要自己书写

  <Teleport>
    可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去

  <KeepAlive>

toValue() 是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数

Mixin的局限性:
  不清晰的数据来源
  命名空间冲突
  隐式的跨 mixin 交流

自定义指令:只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用

Vue 单文件组织,使用 @vue/compiler-sfc 编译成 js 和 css

组合式api:

  shallowRef()的主要用途是性能优化,仅响应顶层变化
    使用场景:
      1.​​性能敏感型数据​:数据规模大(如大型表格、树形结构)且​​只需响应顶层引用变化​​时(如整体替换数据),使用 shallowRef 可显著提升性能
      2.集成外部状态管理​:与第三方状态库(如 Redux、MobX)或非 Vue 管理的对象(如 DOM 元素、类实例)集成时,避免 Vue 的响应式系统侵入外部对象
      3.动态组件切换:
      4.手动控制更新时机:结合 triggerRef() 在深度修改对象后​​手动触发更新​​,实现更精细的性能控制

  reactive():用于创建响应式对象,适用于对象类型数据
  ref():用于创建响应式引用,支持基本类型和对象类型
  computed():创建计算属性
  watch()和watchEffect():用于侦听响应式数据的变化
  生命周期钩子函数:如onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted等
  provide()和inject():实现跨层级组件通信
  readonly()和shallowReadonly():创建只读响应式对象
  toRef()和toRefs():用于将响应式对象的属性转换为ref,避免 reactive 解构丢失响应性
  shallowRef()和shallowReactive():创建浅层响应式对象
  toRaw():获取原始非响应式对象(用于临时操作),可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象
  markRaw():标记对象永不转为响应式(如静态配置)
  customRef():创建自定义ref
  defineProps()和defineEmits():在<script setup>中声明props和emits
  defineExpose():暴露组件内部属性给父组件
  defineOptions():在组合式API中设置组件选项,定义组件名、组件注册等
  defineSlots():定义和访问组件插槽,类型安全的插槽声明

Vue3.0 新特性

  1. Vue2.0 响应式原理
  • Vue2.0 响应式 使用 object.defindPrototy,对data上的数据进行遍历,进行响应式数据劫持,然后把劫持到的数据赋值给当前组件实例化对象上面
    • 存在的问题:
      • 对象动态新增属性、删除属性,界面不会自动更新(需要使用$set、$delete)
      • 数组直接通过下标修改数值,界面不会自动更新(需要使用$set,或者splice方法)
      • 必须要遍历所有的数据,还需要重写数组的方法,性能消耗也比较大
  • 如何监听数组的变化:
    • 原理就是重写数组的七个原始方法,当使用者执行这些方法时,我们就可以监听到数据的变化,然后做些更新操作,
  • Vue3.0 的 响应式(Proxy)
    • 直接对整个对象进行响应式数据劫持,并返回一个新的对象,就可以直接操作新的对象
    • 可以直接监听数组的变化(push、shift、splice)
    • 由于 Object.defineProperty 只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象, 不需要遍历操作
    • 实现原理
      • 通过 Proxy(代理):拦截对象中的任意属性的变化,包括属性的读写、添加、删除等
      • 通过 Reflect(反射):对被代理对象的属性进行操作

    问题:响应式数据丢失
    • 使用 reactive 定义的数据重新赋值
    • 响应式数据被解构赋值(大多是 props 中的数据被解构赋值)
    • 使用vuex的数据进行赋值

    解决:
    • 可以使用 toRefs() 和 toRef() 这两个工具函数
  1. 性能优化
  • diff算法优化
    • Vue 2.0 是全量比较
    • Vue 3.0 新增静态标记,每次创建虚拟 DOM 的时候,会根据DOM中的内容,是否会发生变化,添加静态标记,只比较有静态标记的
  • 静态提升
    • Vue2.0中,无论元素是否发生变化,每次都会重新创建,然后渲染
    • Vue3.0中,对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
  • 事件侦听器缓存、复用
  • SSR(服务端渲染优化)
    • 当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
  1. 按需编译、体积更小
  • vite 实现原理:利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一下预编译,省去webpack冗长的打包时间
  1. Composition API
  • 常用的组合式 Api:
    • setup、ref、reactive、计算属性、侦听器、生命周期、响应式原理、自定义Hooks、toRef、toRefs
  • 其他组合式 Api:
    • shallowRef、shallowReactive、readonly、shallowReadonly、toRaw、markRaw

setup 函数

- setup()函数为 Composition API 的入口,在 beforeCreated 之前执行 ,对象中的属性、方法需要 return 出去

- setup()函数中也可以使用生命周期,setup()相当于组件的 beforeCreate 函数

- setup 函数有两个参数:props 和 context

- setup 函数中如果直接解构 props ,会出现丢失响应性,需要解构 `props` 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 toRefs()和 toRef()

- context(上下文对象)是非响应式的,可以安全地解构

- 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象
- 若要避免这种深层次的转换,请使用 shallowRef() 来替代
- 若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代

5. 更先进的组件

  • Fragment
  • Teleport
  • Suspense
  1. 更好兼容 TS
  • Vue3.0 从底层就支持 TS
  • 通过 defineComponent 定义组件
  • 在 setup 函数中使用 TS 泛型定义 props 和 emits
  • 使用 defineProps 和 defineEmits 在 <script setup> 中定义 props 和 emits

Vue3.0 变化了哪些细节?

  1. ref操作的变化。v2中使用ref属性访问DOM或组件实例,在this.$refs上访问它们;在v3中,使用ref这个组合API,配合ref属性来访问DOM或组件实例;当ref和v-for同时使用时,不再自动收集ref实例了,需要封装自定义收集方法。
  2. 异步组件的变化:v2中使用Promise模拟异步组件;在v3中,使用defineAsyncComponent定义异步组件。
  3. $attrs的变化:v2中用于接收自定义属性们(但不包括class和style);v3中除了自定义属性,可以接收class和style了。使用 $attrs / setupCtx.attrs / useAttrs() 接收。
  4. 自定义指令的变化:v2中使用Vue.directive()定义全局指令;v3中使用app.directive()定义全局指令。v3中自定义指令的钩子们发生系列变化。局部指令仍然使用directives选项。
  5. 创建根实例的系列变化:v3中使用 createApp()创建根实例;创建根实例时如果用到data选项,data只能写成工厂函数;挂载根实例节点时只能使用$mount(),el选项没有了。
  6. 函数式组件的变化:v2中是支持函数式组件的,v3中对函数式组件的支持更加强大。
  7. Vue构造器函数的变化:v2中可以使用Vue这个构造函数,所有全局API都放在Vue上。v3中,不能使用Vue构造函数了,只能使用createApp()来创建根实例。为什么在v3中要隐藏Vue这个构造函数呢?第一个原因,为了避免开发者操作原型链,这会影响Vue应用的性能;第二原因,是为了配合Webpack实现Vue层面上的"Tree-Shaking"功能。虽然在v3中我们不能在原型链上添加API了,在v3中推荐app.config.globalProperties来添加全局数据。
  8. 过滤动画的变化:v3中,编写自定义动画名时,使用.qf-enter-from表示进入动画的开始时刻,使用.qf-leave-from表示离开动画的开始时刻。当对多个元素执行动画时,无须再加key。
  9. 条件渲染的变化:v3中,v-if/v-else-if/v-else在任何地方使用时,都无须手动添加唯一的key值,v3中会自动为节点们加key。
  10. render选项的变化:v2中,render: (h)=>h(App),也就是h函数在render函数的形参中。v3中,render函数没有h这个形参了,把h函数单独变成一个API。
  11. v-if和v-for同时使用的变化:v2中不推荐它两一起用,如果一起用,v-for优化级更高。v3中,这两指令可以一起用,但v-if优先级总是更高。
  12. watch监听器的变化:v3中,watch可以同时监听多个声明式变量的变化,调用watch()返回stop()方法(用于停止监听)。
  13. 接收props的变化:v2中接收props时,如果引用数据类型,default写成工厂函数,在这个工厂函数中可以访问this。在v3中,default工厂函数中没有this,但可以使用inject()。
  14. v-model的变化:v3中,在HTML表单上,v-model用法没有变化,包括语法糖写法和v2是一样的。v3中,在自定义组件上,v-model等于:modelValue+@update:modelValue。在自定义组件上,可以同时使用多个v-model,像这样 <Form v-model:x='' v-model:y=''>。v3中,使用v-model时,可以支持自定义修饰符,在子组件中使用 xModifiers: { default: ()=>({})} 接收自定义修饰符。model选项被淘汰了。
  15. 在使用插槽时,即使是默认插槽,在使用时也要这样 <template #default>。

移除了哪些细节

  1. 移除了 $listeners
  2. 移除了 $children
  3. 移除了 $on/$off/$once 事件API,也就是说“事件总线”被移除了。
  4. 移除了过滤器,没有这个 app.filter(),也没有了filters选项。
  5. 移除了 Vue.config.keyCodes 自定义键盘码的功能。
  6. 移除了 el选项。
  7. 移除了 propsData 选项(从new Vue()向App根组件传递初始化数据),在v3中的替代方案是createApp(App, {数据})的第二个参数。
  8. 移除了 v-on 的.native 修饰符。在v2中,这个修饰符是用于解决移动端事件绑定的性能优化。
  9. 移除了 model 选项。(在v2中,model选项用于自定义 v-model语法糖。)

新增了哪些细节

  1. 新增了emits选项、新增了 defineEmits(),用于在子组件中接收自定义事件。
  2. 视图模板支持多节点了,类似React的Fragment功能。需要注意的是,因为组件支持多节点了,对$attrs/等功能有些影响。
  3. 新增了 getCurrentInstance() ,在组件中访问app根实例以及其全局数据。需要注意的是,这个API所获取到的app实例,和main.js中的那个app不是完全相同的。
  4. 新增了 app.provide() 这个全局API,向整个组件树中注入全局数据。在某种程度上讲,这种玩法可以替代app.config.globalProperties这种玩法。
  5. 新增了 nextTick() 这个API。在v3中,nextTick() 也支持 "Tree-Shaking" 了。
  6. 新增了 <teleport to='HTML标签/id选择器'>,用于把嵌套的HTML渲染到to属性对应的DOM中去。<teleport>不支持对js的穿梭。
  7. 新增了 <suspense> 内置组件。用于给异步Vue组件添加交互提示。常常配合 一起用,给整个系统添加统一的交互(Loading...过渡动画、组件缓存等)。
  8. 新增了 v-memo 指令。用于性能优化。<div v-memo='[依赖列表]'></div> 有且仅有当依赖列表中的变量发变化时,其内部的视图结构才会更新。forceUpdate()可以做到强制v-memo更新。
  9. 在 <style> 中可以使用 v-bind了,v3给了我们第三种实现动态样式的玩法。
  10. 新增了 expose 选项 / defineExpose() ,用于把setup中的指定变量暴露出来,给其它组件进行访问与引用。原因是,在v3中,写setup()和<script setup>中的变量默认是隐藏的,无法被其它组件访问。所以,这两个新增的api就是解决这个问题的。
  11. 新增了 useSlots(),用于在子组件访问插槽作用域。和$slots作用一致。

Vue 3 中的插槽与 Vue 2 的区别

  1. 编译优化
  • 静态插槽提升:Vue 3 会对静态插槽进行编译优化,减少运行时的开销。静态插槽的内容在编译时会被提取出来,避免每次渲染时重新创建。
<!-- 子组件 -->
<template>
  <div>
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

<!-- 父组件 -->
<my-component>
  <template #header>
    <h1>Header</h1>
  </template>
  <p>Default content</p>
  <template #footer>
    <p>Footer</p>
  </template>
</my-component>
  1. 动态插槽名称
  • Vue 3 支持使用动态插槽名称,这在 Vue 2 中是不支持的。
<my-component>
  <template #[dynamicSlotName]>
    <p>Dynamic Slot Content</p>
  </template>
</my-component>
  1. 作用域插槽的简化: Vue 3 简化了作用域插槽的语法,使其更加直观和易用
<!-- 子组件 -->
<template>
  <div>
    <slot :item="item"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      item: { name: 'Example' }
    };
  }
};
</script>

<!-- 父组件 -->
<my-component v-slot="{ item }">
  <p>{{ item.name }}</p>
</my-component>
  1. 默认插槽的简化: Vue 3 中,默认插槽的语法更加简洁,可以直接使用 v-slot 或简写为 #
<!-- 子组件 -->
<template>
  <div>
    <slot></slot>
  </div>
</template>

<!-- 父组件 -->
<my-component>
  <template v-slot:default>
    <p>Default content</p>
  </template>
</my-component>

或者简写为:

<my-component v-slot>
  <p>Default content</p>
</my-component>

或者更简洁的简写:

<my-component #>
  <p>Default content</p>
</my-component>
  1. 性能优化
  • Vue 3 在插槽的渲染和更新方面进行了优化,减少了不必要的 DOM 操作,提高了性能。
  • 例如,Vue 3 会更智能地处理插槽内容的变化,只更新必要的部分
  1. 组合式 API 支持
  • Vue 3 引入了组合式 API,可以在 setup 函数中更灵活地使用插槽。
<template>
  <div>
    <slot :item="item"></slot>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const item = ref({ name: 'Example' });
    return { item };
  }
};
</script>

<!-- 父组件 -->
<my-component v-slot="{ item }">
  <p>{{ item.name }}</p>
</my-component>
  • Vue 3 在插槽方面进行了多项改进,包括编译优化、动态插槽名称、简化的作用域插槽和默认插槽语法、性能优化以及组合式 API 支持。这些改进使得 Vue 3 的插槽更加灵活、高效和易用。希望这些信息对你有帮助!如果有更多问题,请随时提问。

hash 和 history 区别

  • 原理不同
    • hash:通过监听浏览器的 hashchange()事件变化,查找对应的路由规则
    • history:利用 H5 的 history 中新增的两个API pushState()和 replaceState()和 一个 popstate() 事件 监听URL变化
    • history.pushState 浏览器历史纪录添加记录
    • history.replaceState修改浏览器历史纪录中当前纪录
    • history.popState 当 history 发生变化时触发
  • history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致 404 错误

require 与 import 的区别

  • 两者的加载方式不同,require是在运行时加载,而import 是在编译时加载
  • 语法规范不同,require是 CommonJS/AMD规范,import是 ESMAScript6+ 规范

为什么通常不使用 index 作为 key?

  • 使用 index 作为 key 时,可能存在DOM元素没有发生改变,只是顺序发生了改变,但是此时的 key 的值就发生了改变,根据dom diff算法,就会更新所有key变化的DOM元素

Table Of Content

  • v-if 和 v-show 的区别
  • v-model 原理
  • 计算属性、监听器及其二者区别
  • methods 与 computed 区别
  • $nextTick
  • Vue 组件传参
  • vue 的 router 和 route 区别是什么
  • Vue 路由守卫
  • 为什么 data 属性是一个函数而不是一个对象?
  • Vue 的生命周期
  • 父子组件生命周期执行顺序
  • Vuex 工作流程
  • Vuex 数据持久化原理
    • Vue.use() 做了什么工作?
    • Vue 依赖收集
    • 跨域与浏览器同源策略
    • Vue 的 diff 算法
    • diff 算法比较方式
    • Diff 运算的工作流程:(发生在更新阶段)
    • 如何理解虚拟 Dom
    • Vue3.0 新特性
    • Vue3.0 新特性
    • Vue3.0 变化了哪些细节?
    • 移除了哪些细节
    • 新增了哪些细节
    • Vue 3 中的插槽与 Vue 2 的区别
    • hash 和 history 区别
    • require 与 import 的区别
    • 为什么通常不使用 index 作为 key?
ShareShareShareShare
© 桂ICP备2022007988号
© 2025 Implement By Edwin WB Li