Vue Basic Knowledge Points
Vue Basic Knowledge Points
2025-10-21
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
- beforeRouteEnter
为什么 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 中的状态发生变化时(通常是通过 mutation 修改),将最新的状态序列化后存储到本地存储(如 localStorage)中。
- 持久化原理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 新特性
- 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() 这两个工具函数
- 性能优化
- diff算法优化
- Vue 2.0 是全量比较
- Vue 3.0 新增静态标记,每次创建虚拟 DOM 的时候,会根据DOM中的内容,是否会发生变化,添加静态标记,只比较有静态标记的
- 静态提升
- Vue2.0中,无论元素是否发生变化,每次都会重新创建,然后渲染
- Vue3.0中,对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
- 事件侦听器缓存、复用
- SSR(服务端渲染优化)
- 当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
- 按需编译、体积更小
- vite 实现原理:利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一下预编译,省去webpack冗长的打包时间
- 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
- 更好兼容 TS
- Vue3.0 从底层就支持 TS
- 通过 defineComponent 定义组件
- 在 setup 函数中使用 TS 泛型定义 props 和 emits
- 使用 defineProps 和 defineEmits 在
<script setup>中定义 props 和 emits
Vue3.0 变化了哪些细节?
- ref操作的变化。v2中使用ref属性访问DOM或组件实例,在this.$refs上访问它们;在v3中,使用ref这个组合API,配合ref属性来访问DOM或组件实例;当ref和v-for同时使用时,不再自动收集ref实例了,需要封装自定义收集方法。
- 异步组件的变化:v2中使用Promise模拟异步组件;在v3中,使用defineAsyncComponent定义异步组件。
- $attrs的变化:v2中用于接收自定义属性们(但不包括class和style);v3中除了自定义属性,可以接收class和style了。使用 $attrs / setupCtx.attrs / useAttrs() 接收。
- 自定义指令的变化:v2中使用Vue.directive()定义全局指令;v3中使用app.directive()定义全局指令。v3中自定义指令的钩子们发生系列变化。局部指令仍然使用directives选项。
- 创建根实例的系列变化:v3中使用 createApp()创建根实例;创建根实例时如果用到data选项,data只能写成工厂函数;挂载根实例节点时只能使用$mount(),el选项没有了。
- 函数式组件的变化:v2中是支持函数式组件的,v3中对函数式组件的支持更加强大。
- Vue构造器函数的变化:v2中可以使用Vue这个构造函数,所有全局API都放在Vue上。v3中,不能使用Vue构造函数了,只能使用createApp()来创建根实例。为什么在v3中要隐藏Vue这个构造函数呢?第一个原因,为了避免开发者操作原型链,这会影响Vue应用的性能;第二原因,是为了配合Webpack实现Vue层面上的"Tree-Shaking"功能。虽然在v3中我们不能在原型链上添加API了,在v3中推荐app.config.globalProperties来添加全局数据。
- 过滤动画的变化:v3中,编写自定义动画名时,使用.qf-enter-from表示进入动画的开始时刻,使用.qf-leave-from表示离开动画的开始时刻。当
对多个元素执行动画时,无须再加key。 - 条件渲染的变化:v3中,v-if/v-else-if/v-else在任何地方使用时,都无须手动添加唯一的key值,v3中会自动为节点们加key。
- render选项的变化:v2中,render: (h)=>h(App),也就是h函数在render函数的形参中。v3中,render函数没有h这个形参了,把h函数单独变成一个API。
- v-if和v-for同时使用的变化:v2中不推荐它两一起用,如果一起用,v-for优化级更高。v3中,这两指令可以一起用,但v-if优先级总是更高。
- watch监听器的变化:v3中,watch可以同时监听多个声明式变量的变化,调用watch()返回stop()方法(用于停止监听)。
- 接收props的变化:v2中接收props时,如果引用数据类型,default写成工厂函数,在这个工厂函数中可以访问this。在v3中,default工厂函数中没有this,但可以使用inject()。
- 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选项被淘汰了。 - 在使用插槽时,即使是默认插槽,在使用时也要这样
<template #default>。
移除了哪些细节
- 移除了 $listeners
- 移除了 $children
- 移除了 $on/$off/$once 事件API,也就是说“事件总线”被移除了。
- 移除了过滤器,没有这个 app.filter(),也没有了filters选项。
- 移除了 Vue.config.keyCodes 自定义键盘码的功能。
- 移除了 el选项。
- 移除了 propsData 选项(从new Vue()向App根组件传递初始化数据),在v3中的替代方案是createApp(App, {数据})的第二个参数。
- 移除了 v-on 的.native 修饰符。在v2中,这个修饰符是用于解决移动端事件绑定的性能优化。
- 移除了 model 选项。(在v2中,model选项用于自定义 v-model语法糖。)
新增了哪些细节
- 新增了emits选项、新增了 defineEmits(),用于在子组件中接收自定义事件。
- 视图模板支持多节点了,类似React的Fragment功能。需要注意的是,因为组件支持多节点了,对$attrs/
等功能有些影响。 - 新增了 getCurrentInstance() ,在组件中访问app根实例以及其全局数据。需要注意的是,这个API所获取到的app实例,和main.js中的那个app不是完全相同的。
- 新增了 app.provide() 这个全局API,向整个组件树中注入全局数据。在某种程度上讲,这种玩法可以替代app.config.globalProperties这种玩法。
- 新增了 nextTick() 这个API。在v3中,nextTick() 也支持 "Tree-Shaking" 了。
- 新增了
<teleport to='HTML标签/id选择器'>,用于把嵌套的HTML渲染到to属性对应的DOM中去。<teleport>不支持对js的穿梭。 - 新增了
<suspense>内置组件。用于给异步Vue组件添加交互提示。常常配合一起用,给整个系统添加统一的交互(Loading...过渡动画、组件缓存等)。 - 新增了 v-memo 指令。用于性能优化。
<div v-memo='[依赖列表]'></div>有且仅有当依赖列表中的变量发变化时,其内部的视图结构才会更新。forceUpdate()可以做到强制v-memo更新。 - 在
<style>中可以使用 v-bind了,v3给了我们第三种实现动态样式的玩法。 - 新增了 expose 选项 / defineExpose() ,用于把setup中的指定变量暴露出来,给其它组件进行访问与引用。原因是,在v3中,写setup()和<script setup>中的变量默认是隐藏的,无法被其它组件访问。所以,这两个新增的api就是解决这个问题的。
- 新增了 useSlots(),用于在子组件访问插槽作用域。和$slots作用一致。
Vue 3 中的插槽与 Vue 2 的区别
- 编译优化
- 静态插槽提升: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>
- 动态插槽名称
- Vue 3 支持使用动态插槽名称,这在 Vue 2 中是不支持的。
<my-component>
<template #[dynamicSlotName]>
<p>Dynamic Slot Content</p>
</template>
</my-component>
- 作用域插槽的简化: 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>
- 默认插槽的简化: 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>
- 性能优化
- Vue 3 在插槽的渲染和更新方面进行了优化,减少了不必要的 DOM 操作,提高了性能。
- 例如,Vue 3 会更智能地处理插槽内容的变化,只更新必要的部分
- 组合式 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元素