#前端重铸之路 Day2 🔥🔥

221 阅读5分钟

前置涩话

掘金的小伙伴们大家好呀,这里是家里蹲选手sin。很快啊,已经来到第二天,第一天的八股文实在是太多啦,今天还是换个口味,学学热门框架vue吧!

✅ 整理vue2、vue3

vue

vue核心特性

数据响应式、数据驱动、组件化、指令系统
双向绑定原理(MVVM)
ViewModel:负责将数据与视图关联
监听器(Observe):监听数据的属性
解析器(Compiler):对每个元素节点的指令进行扫描解析,根据指令模板替换数据,以及绑定相应的更新函数。

4acd7b633208d7458ba61e90e9c8a90.jpg
通过数据劫持侦测数据变化,发布订阅模式进行依赖收集与视图更新。
observe:实现数据劫持,递归给对象属性,绑定 getter和setter函数,属性改变时,通知订阅者
compile:解析模版,对每个元素节点根据指令模板替换数据,绑定更新函数,添加订阅者,收到通知就执行更新函数
watcher:作为observe和compile之间的桥梁,订阅 observe属性变化的消息,触发compile更新函数
依赖收集,视图用到的数据,这些key都需要收集出来用一个watcher维护它们,多个watcher由一个Dep管理,需要更新时由Dep统一通知。
数据劫持Object.defineProperty()
缺点:对于通过下标修改数组或者给对象新增属性没有劫持。
因为产生的性能代价与用户收益不成正比
数组的增删会改变下标,劫持要重新遍历数组
vue3是用proxy实现数据响应式,直接动态添加新属性仍可以实现数据响应式。proxy并不能监听内部深层次的对象变化,而vue3处理方式是在getter中递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归。
三种解决方案
vue.set(target,propertyName/index,value)
Object.assign()创建新对象,合并原和混入对象
$forceUpdated() 强制刷新,不建议
vue异步更新策略,下一个事件循环批量更新视图。(data某个属性改变不会立即更新)
new Vue的时候会调用_init方法
定义$set、$get、$delete、$watch等方法
定义$on、$off、$emit、$off等事件
定义_update、$forceUpdate、生命周期
调用$mount进行页面挂载
通过mountComponent方法
定义updateComponent更新函数
执行render生成虚拟dom
_update将虚拟dom生成真实dom,渲染到页面
vue的diff
只会同层级比较,循环从两边向中间比较。

template模板的本质

渲染函数的语法糖,用于描述整个渲染过程,最终产生表达界面结构的虚拟节点。
render函数
参数是createElement(一般用h简化),返回值是vnode即虚拟节点。
createElement 有三个参数:
一个HTML标签名、组件选项对象,或者含有了上述任何一种的一个async函数。子节点(必填)
一个与模板中attribute对应的数据对象(属性)
子级虚拟节点,由createElement()构建而成,也可以使用字符串来生成“文本虚拟节点”。

v-model

vue2里本质是v-bind和v-on的语法糖,默认为组件绑定名为value的prop和名为input的事件。可以用修饰符.sync(后面不能用表达式)。
可以用v-bind.sync=“doc”,将doc对象中每一个元素作为一个独立的prop传进去,然后各自添加用于更新的v-on监听器。
:title @update:title => :title.sync
model选项可以更改默认prop名和事件名。
vue3自定义组件上的v-model相当于传递了modelValue(prop)并接收抛出的update:model Value事件。废除了model选项和.sync
可以使用v-model:propName
新增了使用多个v-model
新增了自定义修饰符
blog.csdn.net/qq131950464…

3.4版本推出了defineModel
始终使用-连接的事件名
vdom 创建成本低
虚拟dom就是通过js生成的AST(抽象语法树)
使用vnode,数据变动的node变化,先在vnode修改,然后diff比较,集中一次对dom tree进行修改,减少浏览器重绘及回流
性能好是一方面,可以减少vue体积,提升效率
可以实现SSR

虚拟dom真正原因

1、框架设计 (数据没有关联真实dom元素)
框架是让组件全量更新,如果更新全部真实dom会有效率问题
2、跨平台 (虚拟dom是js对象)
虚拟dom变成真实dom
当组件实例首次渲染,运行render函数生成虚拟dom树,然后根据虚拟dom树创建真实dom树,并把真实dom挂载到页面合适位置,此时,每个虚拟dom对应一个真实dom。
如果一个组件受到响应式数据变化影响,会重新调用render函数,创建新的虚拟dom树,然后新旧对比,vue会找出最小更新量,更新必要的虚拟dom节点,去修改对应的真实dom。

data为什么是函数?

为了保证每个组件data的独立性,每次函数返回一个新对象。

Computed和Watch的区别?

computed不支持异步
computed用于计算属性,根据依赖的数据动态计算 出一个新的值,并具有缓存和自动更新的特性。
watch用于监听数据的变化,执行相应的操作,适合处理异步操作和复杂逻辑。
在选择使用computed还是watch时,需要根据具体 的场景和需求来决定。如果需要根据已有数据计算出 一个新的值,并且希望该值具有缓存和自动更新的特性,可以使用computed。如果需要监听某个数据的 变化,并执行复杂逻辑、异步操作或对多个数据进行 响应,可以使用watch。
watch回调触发时机,会在vue组件更新之前调用。所以watch中访问的dom是更新前的。如果想访问更新后的dom,需要指明flush:'post'
const unwatch = this.$watch('foo',callback)
unwatch() 停止watch

vue方法属性丢失问题?

原因:methods里的方法和模板调用方法不一样(调用的是组件实例方法,来自methods)
实际是组件实例调用methods方法并且bind绑定了vm(组件实例)为this
解决:可以将方法在data里配置

vue中8种组件通信方案

  1. 通过props传递
  2. 通过$emit触发自定义事件
  3. 使用ref
  4. EventBus事件总线
  5. $parent$root(访问根组件的元素与方法)
  6. attrs(props)与listeners(v-on方法)
  7. Provide 与 inject
  8. vuex

vuex

专门为vue应用程序开发的状态管理模式,集中式存储管理应用所有组件的共同状态,可以为多组件数据通信。 vue2通过重写数组的原型方法来实现对数组变化的监听。改造后的Array原型对象重写了如push、pop、shift、unshift、splice、sort和reverse等方法实现监听和响应。无法直接监听直接修改数组某个元素或使用filter和concat对数组的变化。可以用watch选项或者$watch方法手动监听数组变化。

vue中的mixin(混入)

可以将组件可复用功能以对象方式传入mixins选项,如data、components、methods、created、computed等等。
局部混入:通过mixins调用mixin对象
全局混入:vue.mixin()
混入类型

b3ed3fcfd652a7ffd5e1fc0fdd97ca2.jpg
混入生命周期在主文件之前,注意数据覆盖

vue修饰符

表单修饰符
事件修饰符
v-bind修饰符
鼠标按键、键盘按键修饰符
vue.nextTick() 原理:将回调放入微队列。
在下次dom更新循环结束之后执行延迟回调。
使用场景
修改数据后立即得到更新后的dom结构。
第一个参数为回调函数,第二个参数为执行函数上下文。

vue指令

v-once:只渲染一次,性能优化
v-memo:渲染有缓存,性能优化
vue自定义指令
全局注册Vue.directive('指令名字',{对象数据或指令函数})
局部注册在组件options选项中设置directive属性
应用案例
表单防止重复提交 v-throttle
图片懒加载 v-lazy
一键copy功能 v-copy
拖拽指令、页面水印、权限校验
vue过滤器(filter)注册与指令相似
{{message | filterA | filterB('arg1',arg2)}}
应用于单位转换、数字打点、文本或时间格式化
vue3已移除过滤器

slot插槽

让用户拓展组件,更好的复用和定制化
应用于布局组件、表格列、下拉选框、弹框

axios

封装设置接口请求前缀区分环境,约定请求头实现具体业务(如会员业务)携带参数,根据状态码执行不同业务,封装get、post方法,请求拦截器,响应拦截器。 可以使用取消令牌(cancel token)取消请求

Vue Router

routes:配置对象 router.push()里的参数对象如果提供了path,params会被忽略。
router.replace()
router.go()
命名视图(一个路由多个视图)
配置components
路由组件传参
配置props选项
布尔模式:route.params将被设置为组件属性
对象模式:都是组件属性
函数模式:创建函数返回props history模式需要后台配置支持

导航守卫

参数或查询的改变不会触发导航守卫。可以通过观察$route对象来应对这些变化,或使用组件内守卫。
全局前置守卫 导航触发时调用
router.beforeEach((to,from,next) =>{})
一定要调用next()放行这个钩子
next('/home')、next(to)、next({...to,replace: true})都不是放行,作用是中断当前导航,执行新的导航,重新进入路由守卫
全局解析守卫 router.beforeResolve,导航确认前,同时所有组件内守卫和异步路由组件被解析之后就被调用。 全局后置钩子
router.afterEach((to,from) =>{}) 路由独享的守卫,在路由配置上直接定义beforeEnter守卫,参数与前置守卫一样。\

组件内的守卫

cb195dd701def3cb61ed99710a15d83.jpg
beforeRouteEnter可以通过传一个回调给next来访问组件实例。在导航被确认时执行的回调。
next(vm =>{}) 唯一支持

6d938f1a40945ae7b54346755ed2dc6.jpg
路由元信息 meta字段
数据获取
导航完成后获取数据 生命周期created
导航完成前获取数据 组件内守卫
滚动行为 Router实例使用scrollBehavior方法
动态路由
router.addRoute()
router.removeRoute()
面试题
active-class router-link被点击时样式
query刷新页面参数还在,params不会
route和router的区别
$route对象表示当前路由信息,包含了当前URL解析得到的信息(当前路径,参数,query)
router是用于操作路由跳转的实例

fff5e5f755265d30f6632ad7a24c779.jpg
$router是全局路由的实例,是router构造方法的实例。

594884e4f0a4db5dcbb00bab669b706.jpg

配置动态路由

routes: [
    {
        path: '/user/:userId', // 动态路径段
        component: UserProfile,
        props: true // 启用 props 传递
    }
]

//组件中
props: ['userId'], // 从路由的动态部分接收参数(如果启用了 props)

如果你没有启用 props: true,你也可以在组件内通过 this.$route.params.userId 或 this.$route.query.search 来访问这些参数。

vue和react区别

相同:单向数据流,支持SSR,组件化,数据驱动
不同:vue的vdom是追踪每个组件的依赖关系,不会渲染整个组件树,react每当状态被改变,全部子组件都会re-render
vue响应式,react手动setState
vue双向绑定,react单向绑定
Vue和JQuery的区别在哪?为什么放弃JQuery用Vue?
JQuery直接操作DOM,而Vue不直接操作DOM,而是只需操作数据
Vue的虚拟DOM技术,能适配多端
Vue集成了一些库,大大提高开发效率,例如Route、Vuex等等\

vue优化

使用key 稳定且唯一的key
使用冻结的对象 (object.freeze)冻结的对象不会被响应化
使用函数式组件
使用计算属性
非实时绑定的表单项(通过lazy或不用v-model)
保持对象引用稳定
使用v-show替代v-if
使用延迟装载(defer)延迟装载是一个思路,本质就是利用requestAnimationFrame事件分批渲染内容。
使用keep-alive
长列表优化
打包体积优化

vue3

vue3新特性:
速度更快、体积减少、更易维护、更接近原生、更易使用
相比vue2:
重写了虚拟dom实现
编译模板的优化
更高效的组件初始化
update性能提高
SSR速度提高\

一、编译阶段优化​

  1. ​静态提升(Static Hoisting)​

    • 编译时识别静态节点(无动态绑定的元素),将其提升到渲染函数外部,避免重复创建。
  2. PatchFlag 标记动态内容​

  • 为每个动态节点添加二进制标记(如1代表文本动态,8代表props动态),Diff时仅比对标记部分。

二、运行时优化​

  1. ​Block Tree 区块树​

    • 将模板划分为嵌套的"Block",每个Block追踪其动态子节点(通过dynamicChildren数组)。
    • Diff时跳过静态Block,仅对比动态子树,复杂度从O(n)降至O(动态节点数)。
  2. ​事件缓存(Cache Handlers)​

  • 对事件处理器进行缓存,避免每次渲染重新生成函数。

三、组合式API协同优化​

  1. ​更细粒度的响应式触发​

    • 基于Proxy的响应式系统可精确追踪依赖,结合PatchFlag实现靶向更新。
    • 对比Vue 2的"全量比对"模式,性能提升显著。
  2. ​SSR友好性​

    • 静态节点在服务端渲染时直接输出字符串,客户端激活(hydration)阶段跳过静态内容。

新功能:
fragments、Teleport、composition Api、createRenderer、suspense

fragments可以让模板不用一个div包裹,可以使用<></>
teleport 传送门允许我们将组件内容渲染到dom树任意位置,而不止是自身的父级元素。
使用: 这时无论属于哪个组件,teleport里的内容都会渲染到body里。
createRenderer的作用是: 实现vue3的runtime-core的核心,不只是仅仅的渲染到dom上,还可以渲染到canvas,webview等指定的平台
suspense(实验性)

43508679501e5c265f74398ca75f25e.jpg

异步组件

defineAsyncComponent

生命周期

与vue2相比变化不大,beforeCreate、created两个钩子被setup()替代
onBeforeMount()、onMounted()
onBeforeUpdate()、onUpdate()
onBeforeUnmount()、onUnmounted()

响应式基础

ref()
ref()函数声明响应式状态,ref可以持有任何类型的值,包括深层嵌套的对象、数组或者JavaScript内置的数据结构,比如map。
非原始类型将通过reactive()转换为响应式代理。
也可以通过shallowRef来放弃深层响应性。
reactive()
与将内部值包装在特殊对象中的ref不同,reactive()将使对象本身具有响应性。 reactive里的ref不需要.value
与浅层ref类似,可以用shallowReactive()API退出深层响应性。
reactive()返回的是一个原始对象的proxy,只有代理对象是响应式的。
不能直接赋值,会破坏响应代理对象
解决:数组可以push加解构 arr.push(...newArr)
用对象包裹数组 {arr: [ ]}
Object.assign()
readonly和shallowReactive都会受原始对象影响,直接更改会影响。
ref()和reactive()的区别
reactive()只能适用于引用类型,ref()都可以
ref()使用.value形式进行访问,模板可以不用
对于watch的深度监听,ref()需要加deep,reactive()不需要。
isRef()判断是否为ref对象
triggerRef()强制重新渲染
customRef自定义ref
748995b3d1159ffe5d3cd6f07822cdc.jpg
toRef() 解构某一个值,变成ref对象(保持响应式),对非响应式对象没用
toRefs() 解构某一些值,都变成ref对象
toRaw() 解构某一些值,都变成普通值,用在reactive上,在ref上不起作用
markRaw 将一个对象标记为普通对象,使其永远不会变成响应式对象
用于给响应式对象新加一个不需要响应性的对象

watch

只能监听四种数据:
ref定义的数据
reactive定义的数据
getter函数(return一个值的函数)
包含以上三种的数组
reactive默认开启deep模式
watch侦听对象某个单一值时,需要用一个函数返回这个值,如果这个单一值也是对象,想监听内部需要开启deep。
flush属性:pre 组件更新之前调用
sync 组件同步调用
post 组件更新之后调用
const stop = watchEffect((oninvalidate) => {
oninvalidate(() => {}) 会在最前面执行
},{
flush:'post', 刷新时机
onTrigger () { } 调试
})
stop() 停止监听
watchEffect里面用到哪些,就自动监听这些数据,默认立即执行一次
watchEffect原理:传入一个函数F,立即执行函数F,并自动监听函数用到的响应式数据,数据变化时再调用函数F
注意点:响应式数据不能在异步代码下面

标签的ref属性

用在HTML上,为dom元素
用在组件上,为组件实例
父组件可以使用子组件使用defineExpose暴露的数据

传值

父子组件传值
defineProps() defineEmit()

//PropType:Vue 提供的类型工具,用于在运行时声明中指定复杂类型
import type { PropType } from "vue";
import type { ApplyBasicInfo } from "@/interface";
 
const props = defineProps({
  applyBasicInfo: {
  //as PropType<ApplyBasicInfo>:类型断言,告诉 TypeScript 这个对象的具体结构
    type: Object as PropType<ApplyBasicInfo>, // 使用标准的PropType进行对象类型定义
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  operateCommandType: {
    type: String,
    default: "info-view" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});

vue3.3增加了defineProps泛型的支持,在script增加generic='T'
defineProps<{ name:T[ ] }>()
defineEmits<{ 'send':[name:string] }>()
$attrs v-bind='$attrs' 将子组件没有接收的prop传给孙组件 孙组件用defineProps接收
$refs,值为对象,包含所有被ref标识的dom元素和组件实例
$parent,值为对象,当前组件的父组件实例
需要子或父使用defineExpose暴露属性和方法
依赖注入provide/inject 依赖提供后,可以注入所有的子孙组件。

<!-- 在供给方组件内 -- > 父组件
<script setup>
import { provide, ref } from 'vue'
 
// 数据、状态
const location = ref('North Pole')
 
// 变更状态的函数
function updateLocation() {
  location.value = 'South Pole'
}
 
// 提供数据和操作方法(function)
provide('location', {
  location,
  updateLocation
})
</script>
<!-- 在注入方组件 --> 子组件
<script setup>
import { inject } from 'vue'
 
// 被注入(得到)状态和方法
const { location, updateLocation } = inject('location')
</script>
 
<template>
  <!--调用函数修改状态-->
  <button @click="updateLocation">{{ location }}</button>
</template>

Bus总线
mitt
defineOptions () 里面属性和optionsAPI一样
defineSlots 只有声明没有实现,没有任何参数,只能接受ts的类型。约束slot的类型,并进行类型提示。

pinia

store是一个reactive对象
直接解构store会破坏响应性,可以用storeToRefs(),它将为每个响应式属性创建引用。
可以直接解构action,因为它们被绑定在store上
store.$reset()将state重置为初始值
store.$patch()可以更改一个或多个state属性,也可接受一个函数,参数为state,对对象和数组进行操作。
可以通过变更pinia实例的state来设置整个应用的初始state,常用与SSR的激活过程。
store.$subscribe()可以侦听state及其变化,比起普通watch(),好处是subscription在patch后只触发一次。如果想在组件卸载后仍然保留,可以将{ detached:true }作为第二个参数。
const unsubscribe = store.$onAction()可以监听action和它们的结果。传递的回调函数会在action本身之前执行,after在action成功并完全运行后触发,onError在action抛出或返回一个拒绝的promise时触发。保留监听可以传第二个参数true。
手动删除监听器unsubscribe()

defineModel()

548b136fa1881454d1d04a45c5c287b.jpg
使用defineModel

aeeccc89464950c205c9637d3a31d81.jpg
defineModel('model', {required: true})
自定义修饰符,并用get和set进行处理

2cb5bd86f2b71746da32061ab84ba66.jpg

插槽

默认插槽
具名插槽
v-slot:xxx(简写#xxx)只能写在组件或者template上
作用域插槽 子组件通过将数据传给父组件的<template #xxx=data>,由父组件决定数据展示的结构。

useAttrs 和 props 的区别?

props:只会接收props类型定义的属性 useAttrs:只会接收非 props 类型定义的属性 是一个内置组件,它的功能是在多个组件间动态切换时缓存应被移除的组件实例。

动态组件

</component> :is的值可以是被注册的组件名、导入的组件对象

样式

:deep()样式穿透
原理:将[data-v-xxxxx]转移到主组件 :slotted() :global () postCss css自己的Babel unoCss原子化

vue中的宏到底是什么?

vue3的宏是一种特殊的代码,在编译时会将这些特殊的代码转换为浏览器能够直接运行的指定代码,根据宏的功能不同,转换后的代码也不同。 为什么这些宏不需要手动从vue中import? 因为在编译时已经将这些宏替换为指定的浏览器能够直接运行的代码,在运行时已经不存在这些宏相关的代码,自然不需要从vue中import。 为什么只能在setup顶层中使用这些宏? 因为在编译时只会去处理setup顶层的宏,其他地方的宏会原封不动的输出回来。在运行时由于我们没有在任何地方定义这些宏,当代码执行到宏的时候当然就会报错。

vue3.5新特性

Vue 3.5 带来了多项重要更新,涵盖性能优化、新 API 和开发体验改进。以下是主要新特性总结:


​1. 响应式系统重构​

  • ​性能提升​​:响应式追踪效率提高 3 倍,内存占用减少 56%

  • ​大型数组优化​​:常见数组操作速度提升 10 倍

  • ​响应式解构​​:defineProps 解构的属性默认保持响应性,无需 toRef

    const {value = 0, label = 'label'} = defineProp<{
        value: number
        label: string
    }>()

​2. SSR 增强​

  • ​懒加载水合​​:hydrateOnVisible 实现组件可见时再水合,降低初始负载

  • ​唯一 ID 生成​​:useId() 确保 SSR/客户端 ID 一致性,避免水合不匹配


​3. 新 Composition API​

  • useTemplateRef​:动态绑定模板引用,支持灵活 DOM 操作

  • ​观察者清理​​:onWatcherCleanup 注册清理回调,避免内存泄漏

  • ​暂停/恢复观察​​:watch 新增 pause() 和 resume() 方法和deep属性表示监听层数


​4. 自定义元素改进​

  • shadowRoot​:boolean,默认为 true。设置为 false 以在不带 shadow root 的情况下渲染自定义元素。这意味着自定义元素单文件组件中的 <style> 将不再被封装隔离
  • nonce 支持​​:增强样式标签的安全性,该选项将加到自定义元素注入的<style>标签上
  • configureApp​: 一个函数,可用于配置自定义元素的 Vue 应用实例。
  • 新增了useHost() useShadowRoot() this.$host等api

​5. Teleport 增强​

  • ​延迟挂载​​:<Teleport defer> 支持目标元素延迟渲染

​6. 工具链升级​

  • ​Vite 6​​:构建速度提升 10 倍,支持 WebAssembly/WebGPU

  • ​Pinia 3​​:全面适配 Composition API,状态管理更轻量


​7. TypeScript 与 DevTools​

  • ​类型推断优化​​:组合式 API 类型支持更精准

  • ​DevTools 集成​​:新增性能分析面板和组合式 API 跟踪


​8. 其他改进​

  • v-memo 指令​​:缓存组件渲染结果,优化重复渲染

  • ​错误处理增强​​:完善 setup 和生命周期的错误捕获