zerojs前端面试题系列--vue3篇(3)

1. Vue3 中的响应式系统是如何工作的?

Vue3中的响应式系统采用了Proxy代理对象来实现。其实现原理基本上是将所有响应式数据转换成Proxy对象,并在Proxy对象内部添加get和set方法来监听数据的变化。当数据发生变化时,Proxy对象会自动触发相应的更新操作,从而更新相应的视图。

具体来说,当我们在Vue3中定义一个响应式变量时,比如:

import { ref } from 'vue'

const count = ref(0)

Vue3会将count变量转换成一个Proxy代理对象,代理对象内部会监听count变量的get和set方法:

const count = {
  __v_isRef: true, // 标记为ref对象
  get value() {   // 监听获取值操作
    track(count, TrackOpTypes.GET, 'value')
    return count.__v_value
  },
  set value(newValue) { // 监听设置值操作
    if (newValue !== count.__v_value) {
      count.__v_value = newValue
      trigger(count, TriggerOpTypes.SET, 'value', newValue)
    }
  }
}

其中,track函数用来记录响应式数据被访问的情况,trigger函数则用来触发数据更新和通知所有关联的依赖。

当我们在模板中使用响应式变量时,Vue3的编译器会自动将其转换成对Proxy对象的访问,从而实现了数据和视图的双向绑定。

总之,Vue3的响应式系统利用了Proxy代理对象的特性来实现了数据的双向绑定,可以更加高效、灵活地处理数据变化和视图更新。

2. 在vue3中, ref 和 reactive有什么区别?

ref()

在组合式 API 中,推荐使用 ref() 函数来声明响应式状态:

import { ref } from 'vue'

const count = ref(0)

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
ref()的深层响应性

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map

Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:

import { ref } from 'vue'

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // 以下都会按照期望工作
  obj.value.nested.count++
  obj.value.arr.push('baz')
}

reactive()

还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

非原始值将通过 reactive() 转换为响应式代理,该函数将在后面讨论。

也可以通过 shallow ref 来放弃深层响应性。对于浅层 ref,只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。

Reactive Proxy vs. Original

值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的:

const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是 仅使用你声明对象的代理版本

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:

const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false
reactive() 的局限性

reactive() API 有一些局限性:

  1. 有限的值类型:它只能用于对象类型 (对象、数组和如 MapSet 这样的集合类型)。它不能持有如 stringnumber 或 boolean 这样的原始类型

  2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

    let state = reactive({ count: 0 })
    
    // 上面的 ({ count: 0 }) 引用将不再被追踪
    // (响应性连接已丢失!)
    state = reactive({ count: 1 })
    
  3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

    const state = reactive({ count: 0 })
    
    // 当解构时,count 已经与 state.count 断开连接
    let { count } = state
    // 不会影响原始的 state
    count++
    
    // 该函数接收到的是一个普通的数字
    // 并且无法追踪 state.count 的变化
    // 我们必须传入整个对象以保持响应性
    callSomeFunction(state.count)
    

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

总结

官方文档建议:因为reactive()的局限性,所以建议使用 ref() 作为声明响应式状态的主要 API.

3. 在vue3中,什么是 toRef 和 toRefs?它们分别用于什么场景?

在Vue 3中,toRef和toRefs是两个新的响应式API,用于将普通的JavaScript对象或原始值转换为响应式对象。

具体来说,toRef将一个普通的JavaScript对象的属性转换为一个响应式对象,而toRefs则将整个JavaScript对象转换为一组响应式对象。

toRef

toRef 方法用于将普通JavaScript对象的属性转换为响应式对象。它接受两个参数:第一个参数是要转换的对象,第二个参数是属性名称。

示例:

import { reactive, toRef } from 'vue'

const state = reactive({
  name: 'Tom',
  age: 20
})

const nameRef = toRef(state, 'name')
console.log(nameRef.value) // 'Tom'

在上面的例子中,我们将state对象的name属性转换为一个响应式对象,可以通过访问nameRef.value来获取它的值。如果原始对象的属性发生了更改,那么toRef返回的响应式对象也会自动更新。

toRefs

toRefs 方法用于将整个JavaScript对象转换为一组响应式对象。它接受一个参数:要转换的对象。

示例:

import { reactive, toRefs } from 'vue'

const state = reactive({
  name: 'Tom',
  age: 20
})

const refs = toRefs(state)
console.log(refs.name.value) // 'Tom'
console.log(refs.age.value) // 20

在上面的例子中,我们将整个state对象转换为响应式对象组,可以通过访问refs.name.valuerefs.age.value来获取属性的值。如果原始对象的属性发生了更改,那么返回的响应式对象也会自动更新。

toRef和toRefs的使用场景:
  • 当我们需要将一个普通的JavaScript对象或原始值转换为响应式对象时。
  • 当我们需要访问响应式对象的某个属性时,可以使用toRef将其转换为响应式对象。
  • 当我们需要同时访问多个响应式对象时,可以使用toRefs将整个JavaScript对象转换为响应式对象组。

4. 在Vue 3中,如何检查一个值是否为ref对象?

在Vue 3中,可以使用isRef来检查一个值是否为ref对象。isRef是Vue 3中的内置函数,用于检查一个值是否为ref对象。

例如,以下代码演示了如何使用isRef检查一个值是否为ref对象:

import { isRef } from 'vue';

const myRef = ref('my value');
const myValue = 'not a ref';

console.log(isRef(myRef)); // true
console.log(isRef(myValue)); // false

在上面的示例中,isRef函数检查myRef是否为一个ref对象,结果为true,而检查myValue,结果为false,因为myValue不是一个ref对象。

5. 请解释下Vue3中的isRef、isReactive、isReadonly和isProxy函数的作用和使用方式?

Vue 3 中的 isRefisReactiveisReadonlyisProxy 函数都是用于判断数据是否符合特定规则的函数,每个函数的作用如下:

  • isRef:判断一个数据是否为响应式引用对象。
  • isReactive:判断一个数据是否为响应式对象。
  • isReadonly:判断一个数据是否为只读响应式对象。
  • isProxy:判断一个数据是否为代理对象。

这些函数的使用方式都很简单,只需要传入一个数据作为参数,函数会返回一个布尔值,表示该数据是否符合对应规则。下面分别给出一个示例:

import { isRef, isReactive, isReadonly, isProxy, reactive, ref, readonly } from 'vue'

const data = reactive({
  name: 'Tom',
  age: 18
})

const refData = ref(data)

const readonlyData = readonly(data)

const proxyData = new Proxy(data, {})

// 判断数据是否为响应式引用对象
console.log(isRef(refData)) // true
console.log(isRef(data)) // false

// 判断数据是否为响应式对象
console.log(isReactive(data)) // true
console.log(isReactive(refData)) // false

// 判断数据是否为只读响应式对象
console.log(isReadonly(data)) // false
console.log(isReadonly(readonlyData)) // true

// 判断数据是否为代理对象
console.log(isProxy(data)) // false
console.log(isProxy(proxyData)) // true

需要注意的是,isRefisReactiveisReadonlyisProxy 函数只是用于判断数据是否符合对应规则,不能用于修改数据或创建响应式数据。如果要创建响应式数据,需要使用 reactiverefreadonly 等创建函数。

6. 在vue3中为什么要使用toRef和toRefs来解构reactive()数据?

在Vue 3中,使用 reactive 函数来创建响应式对象,但是当我们想要对其进行解构时,有时会遇到一些问题。

如果我们简单地使用解构语法对 reactive 对象进行解构,那么解构后得到的结果就不再是响应式的了。也就是说,当我们修改解构后的对象中的属性时,这些修改并不会反映到原始的响应式对象中。

为了解决这个问题,Vue 3提供了两个函数 toReftoRefs

toRef 函数接收两个参数:第一个参数是响应式对象,第二个参数是要访问响应式对象上的属性名。它返回一个代表响应式对象上这个属性的只读的 ref 对象。我们可以通过解构这个只读的 ref 对象来获取响应式对象上的属性,并且这样做得到的结果是响应式的。

举个例子:

import { reactive, toRef } from 'vue'

const state = reactive({ count: 1 })

const countRef = toRef(state, 'count')

console.log(countRef.value) // 输出 1

如果我们使用简单的解构语法来获取 count 属性:

const { count } = state

那么我们可以得到一个普通的变量 count,而不是一个响应式变量。也就是说,当我们修改 count 变量的值时,它并不会反映到 state 对象上。

相反,如果我们使用 toRef 函数来获取 count 属性:

const countRef = toRef(state, 'count')

那么我们得到的是一个只读的 ref 对象。我们可以通过解构这个 ref 对象来获取 count 属性:

const { value: count } = countRef

这样得到的 count 变量就是响应式的了。也就是说,当我们修改 count 变量的值时,它会反映到 state 对象上。

toRefs 函数则可以将一个 reactive 对象转换成一个普通的对象,但是对象里的每一个属性都被转换成了 ref 对象。这样做的好处是,我们可以在不改变属性原有结构的情况下,让这些属性变成响应式的。

举个例子:

import { reactive, toRefs } from 'vue'

const state = reactive({ count: 1 })

const refs = toRefs(state)

console.log(refs.count.value) // 输出 1

toRefs 函数会将 count 属性转换成一个只读的 ref 对象,我们可以通过 refs.count.value 来访问这个属性。当我们修改 refs.count.value 的值时,它会反映到 state 对象上,因为它仍然是 state.count 的一个引用。这样,我们就可以在不改变原有对象结构的情况下,让对象的所有属性变成响应式的。

总之,使用 toReftoRefs 函数可以帮助我们在解构响应式对象时保持响应性。它们都提供了一种简单的、可靠的方式来解构响应式对象,并确保访问结果是响应式的。

小程序刷题

搜索: zerojs零技术

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值