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方法将需要暴露的状态暴露出去,从而也就极大可能的规避了外部修改内部状态的机会。