vue3的理解

vue和react的区别

  • vue是mvvm框架,也就是model-view-viewModel使用的是双向绑定,通过viewModel这个桥梁用于组件和数据之间进行绑定,从而组件更新数据也随之更新,数据更新组件也随之更新,可以实现组件和数据同时更新。并且尽可能的实现组件的局部更新
  • react,使用的是单向数据流,是由数据驱使组件的更新,每次状态更新之后,然后都会重新执行render函数。会使整个组件都重新渲染
  • 优缺点:
    • react使用了hooks函数式组件开发,使每个函数都可以return一个组件,开发起来较为灵活;并且react社区比较庞大支持很多第方三的库,可以开发一些相对来说比较复杂的页面。开发灵活带来的缺点是react在协作开发时必须要统一代码风格,不然这一块那一块较零散的代码,很容易写成屎山,导致不好维护,对于刚接手项目的不太好友好,熟悉项目周期会更长一点。
    • vue使用双向绑定可以使组件状态进行局部更新,代码风格开发规范以及生态都比较成熟,维护起来比较好维护,只要按照开发规范来就不容易写成屎山代码。但是这个优点同时也算是vue的一个缺点,开发起来不太灵活,代码格式较为死板,但是vue3中加了Composition API以及setup语法糖既可以实现模块化的开发又可以使代码写的更加灵活,同时也使框架性能进一步更高。

vue的生命周期

diff 复制代码
- beforeCreate:组件初始化之前
- created:组件初始化完成,可以在这里调用接口,访问各种数据
- mounted:dom已经挂载完成,可以获取到dom元素
- beforeUpdate:组件更新之前,能获取到更新之前的状态
- updated:组件更新完成,能获取到最新的状态
- beforeUnmount:组件销毁之前,通常用于清除一些监听订阅等方法
- unmounted:组件已经销毁,通常用于清除一些监听订阅等方法

v-for和v-if同时使用,vue2和vue3的区别

  • vue2同时使用,v-for优先级>v-if,这样会导致在list每次渲染的时候,都会重新触发一次遍历,性能会受到影响。
  • vue3同时使用,v-if优先级>v-for,这样会导致v-if拿不到某个元素的变量,因为有可能该元素还没循环到,会导致冲突。
  • 一般不建议同时使用,可以先将数组根据条件过滤掉之后再进行渲染,或者使用v-show进行条件判断

diff算法,vue2和vue3的区别,以及有key和无key的区别

  • 无key分为三步:1.新旧虚拟dom做对比,dom属性和位置有更新的话新的会把旧的替换掉。2.如果发现多的,会进行新增。2.发现少的会进行删除。(只根据dom对象属性和位置做对比,当然其中vue3还用到了dom对象的静态提升做了性能优化)
  • 有key分为五步:1.如果dom对象有更新的话就先进行前序算法,判断新旧dom的key和dom的类型是否都一样,一样就替换,不一样就跳出循环进行尾序算法。2.尾序算法,和前序算法基本相同,只不过是从尾部开始找。3.如果发现多的,会进行新增。4.发现少的会进行删除。(这里跟vue2的diff算法有升级,vue2会进行交叉做对比,而vue3对比不会做头尾交叉了)5.如果发现dom节点的位置或者映射关系改变的话,会根据"最长递增子序列算法"进行dom的移动,这一算法能够更有效地识别节点的移动,减少不必要的dom更新进一步优化性能。。也就是说,有key就根据key能更精准快速有效的找到新旧dom做比对,无key的话就只能根据位置和对象属性做对比,从而减少了不必要的比较,以及减少了不必要的性能开支

静态提升

  • 首先vue在编译的时候会把我们的组件编译成render函数。静态提升就是在编译的时候vue会把组件的静态节点以及静态属性单独提出来然后赋值给某个变量,不让它在render函数中运行。因为vue每次执行render函数或重新渲染的时候都会去创建一个vnode。因为静态节点是不会变的没必要每次render的时候去创建,使用静态提升的话就只会创建一次vnode然后赋值给某个变量,然后在render函数中直接使用这个变量就行了。
js 复制代码
//vue2
render(){
    createVnode('H1',XXX,'xxx内容')
}

//vue3
const staticObject = createVnode('H1',XXX,'xxx内容')
render(){
    //render里面直接使用staticObject就行 
}

ref和shallowRef的区别

  • ref:使用ref响应式数据修改数据时候必须 object.value.xxx = xxx 才能修改属性(深层的响应)
  • shallowRef:使用shallRef响应式数据修改数据的时候需要object.value = { xxx:xxx }才能更新数据以及更新视图,如果使用object.value.xxx = xxx,只会更新数据,但是视图不会更新(浅层的响应)
  • 但是如果说,一个方法里同时修改ref和shallowRef的响应式数据,shallowRef会被影响(意思是这个时候两者都使用object.value.xxx = xxx 这种方式修改属性,会影响到shallowRef的视图更新,因为修改ref响应式数据的时候会触发依赖的更新,然后在重新渲染的时候它会把所有的视图依赖都进行更新,所以就把shallowRef的依赖一块更新了)

ref和reactive的区别

  • ref:在使用的时候需要加上object.value.xxx、ref支持传入任何类型

  • reactive:在使用时只需object.xxx、reactive只支持传入引入类型(对象类型),reactive的proxy对象不能直接赋值,否则会破坏响应式的规则,如果真的要修改的话必须将reactive传入一个对象包裹的数据,从而修改reactive响应式数据的对象属性

  • 为什么ref要.value,而reactive不需要:ref返回的是一个RefImpl对象,通过class类的get 和set 属性用来获取更新响应式数据,其中监听的是value属性,所以要使用.value来操作响应式。reactive不需要因为,reactive返回的是个直接就是一个proxy代理对象,直接能操作对象里面的属性

  • 本质上其实也没有什么区别,只不过ref是用来定义基本数据类型响应式,reactive是用来定义引用数据类型响应式,如果ref硬要传入引用数据类型的话,其实走的还是创建reactive响应式数据的逻辑。

  • ref的简洁的源码实现

js 复制代码
function meRef(value) {
  // 如果当前传入的值是ref响应式类型,直接返回当前传入的值
  if (isRef(value)) {
    return value
  }
  // 如果传进来的是个对象,利用reactive生成一个代理对象
  if(isObject(value)){
     const val = reactive({value}
  }
  

  return {

         // 利用new RefImpl创建对象里的get 属性来收集依赖
    get: () => { 
      track(this)
      return val.value
    },
  	// 利用new RefImpl创建对象里的set 属性来触发更新依赖 
    set: (newValue)=>{
      // 用于触发响应式数据的更新
      trigger(this)
      val.value= newValue
    }
  }
}
  • reactive的简洁源码实现
js 复制代码
const valueMap = new WeakMap()
function myReactive(value: object) {
  // 检查传入的参数是否是一个对象
  if (!Object.prototype.toString.call(value).includes('Object')) {
    // 如果不是就直接返回
    return value
  }
  // 如果存在缓存值就直接返回缓存里的值
  if (valueMap.has(value)) {
    return valueMap.get(value)
  }
  // 如果以上都不成立,就会new一个代理对象,第一个参数为要代理的对象,第二个参数为代理规则(拦截器,可以添加一些扩展方法,而不像Object.defineProperty()只有getter和setter方法)
  const newValue = new Proxy(value, {
    // 当访问当前对象值的时候会调用get方法,并将这个值当作依赖收集起来, get接收当前传入的对象,还有当前访问的key值,返回当前返回的值
    get(value, key) {
      return value[key]
    },
    // 当要改变当前访问的数据,会触发依赖更新,调用set方法,将获取到最新的数据重新赋值给当前操作的对象中的某个值
    set(value, key, newValue) {
      value[key] = newValue
      return true
    }
  })

Object.defineProperty()和Proxy代理对象来实现数据响应式有什么区别与提升

  • Object.defineProperty()使用的是getter和setter方法进行依赖的收集和更新,收集的对象里每个属性,需要对对象里面的每个属性都进行监听,会将对象里的每个属性都要遍历一遍,如果某个值改变之后,再赋值从而会非常的损耗性能。并且Object.defineProperty()是在js对象创建,以及赋值之前就已经被监听了,所以无法对响应式对象进行新增和删除属性的处理,因为在新增和删除属性之前,监听操作已经监听结束了。
  • proxy代理会监听整个对象,在访问响应式数据对象某个值得时候,通过get和set等方法拦截到对象的某个值然后做收集和更新处理,这样即避免了遍历整个对象又可以准确的追踪到key值,从而单独对某个key值进行取值或赋值,减少了计算的损耗
  • 这个也是vue3相对于vue2的一个巨大的提升吧

toRef和toRefs

  • toRef:就是能将响应式对象或者普通对象里的属性单独提出来,并创建一个新的响应式对象,用法:toRef(proxyObj,'key'),如果传个普通对象的话数据会更新,但是视图不会更新,因为虽然toRef底层和Ref基本一样,只不过toRef的底层没有调用"track"和"trigger"方法,没有收集依赖和触发依赖的更新的操作,所以就只有数据更新但是触发视图的更新。
  • toRefs:就是内部将代理对象的每个键值对做toRef的循环处理,循环创建新的引用响应式对象,可以实现数据的解构,用法:const {a,b,c} = toRefs(proxyObj)
  • toRaw:就是将代理对象脱掉外层proxy,将代理对象转换为原始对象,用法toRaw(proxyObj),也就是说只是通过"__v_raw"这个key从代理对象中将原始对象取出来了

computed计算属性的原理

  • computed也通过Proxy代理的get和set方法将计算依赖的值进行收集更新。在取值或者计算的时候,它会先从缓存里拿值计算,如果缓存里没有就把计算的值和依赖项以及计算逻辑保存在缓存里,只有依赖发生改变的时候才会重新计算。某种意义上讲vue3的computed类似于react的useMemo,都是通过监听依赖并进行缓存,只有依赖项发生变化时才会重新进行计算,都起到性能优化的作用。但是computed监听不到异步数据的变化,因为计算属性的依赖变化计算结果也要更新,为了不影响计算的结果,所以必须是同步计算。
  • 相对于vue2的computed来说,vue3的computed进行了很大的优化,vue3利用了proxy代理对象的依赖追踪和更新机制,减少了不必要的对象遍历,并能更准确的追踪到依赖的变化。并使用缓存机制,减少了不必要的计算,使代码的性能得到了进一步的提升。

watch监听属性的原理

  • watch可以监听组件内部的任何属性,只要传入的属性有任何变化,都会执行watch函数。然后通过拿到新旧值做对比去执行一些方法,watch也可以监听到异步数据的变化,但是内部没有像computed一样做缓存。如果是ref响应式,wacth不会深度监听,必须通过deep属性来实现深度监听。某种意义上和useCallback相似,只有传入的数据有变化才会执行某个回调,但是watch返回的是一个停止监听的函数,可以通过调用此函数来停止对某些数据的监听,而且watch是一个惰性函数,可以通过immediate属性来使watch创建时就默认执行一次回调,第一次的旧值是undefinde。同时onTrack / onTrigger也方便开发时的调试。需要注意的是在使用watch监听属性的单个属性的时候,依赖需要传入一个函数,该函数返回这个属性的单个函数
  • 相对于computed来说,watch的使用场景更加灵活,可以监听异步的数据,也可以通过监听到新值旧值做对比来决定下一步代码该怎么执行,但是watch没有使用缓存机制,导致每次执行要多消耗一部分的内存。所以在操作异步数据并且需要进行逻辑判断的情况下推荐用watch,否则直接用computed就可以了。

watchEffect

  • watchEffect会自动追踪函数内部使用的相使用的数据,当内部追踪的数据变化之后会重新执行,watchEffect的基本原理和watch相同,内部同样没有使用缓存机制,但相比watch写法更加灵活简洁,从而降低开发的心智。但是watch可以获取到某个属性的新值和旧值,能准确的监听到某个单个属性的变化。

setup语法糖和setup函数的区别

  • 使用setup函数的编译结果是原封不动的,他会所有组件内部定义的函数以及状态全部都暴露出来,这就给了外部可以操作组件内部的属性机会,开发可以在组件外部直接通过ref拿到某个组件的实例,然后通过$ref.xxx操作组件内部的状态从而也就给了开发违反开发规范的机会,因为平时在开发中组件中的状态最好是只在组件内部进行处理的。
  • 使用setup语法糖的编译结果会多出一个expose函数,这个是用于手动暴露组件内部的状态,默认暴露出去的是一个空对象,表示没有暴露任何组件内部的状态,如果真的需要暴露组件内部状态就需要通过definExpose方法将需要暴露的状态暴露出去,从而也就极大可能的规避了外部修改内部状态的机会。
相关推荐
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普3 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端