Vue3 使用过程中的疑问

358 阅读3分钟

为什么 reactive 创建的对象解构会失去响应式?

例如:

export default {
  setup() {
    const obj = reactive({
      foo: 1,
      bar: 2,
    })
    return {
      ...obj
    }
  }
}
<template>
  <p>{{ foo }} / {{ bar }}</p>
</template>

foo bar 不具有响应式,也就是响应丢失。当我们修改响应式数据时,例如 obj.foo,不会触发重新渲染。

首先要深刻理解 Proxy。reactive 创建了普通对象的 Proxy 对象,也就是代理对象:

// 普通对象
const plainObj = {
  a: 1,
  b: 2,
}

// 代理对象
const proxyObj = new Proxy(plainObj, {
  get(target, key) {
    console.log('get: ', key);
    return target[key]
  },
  set(target, key, value) {
    console.log('set: ', key, value);
    target[key] = value
  }
})

代理对象和普通对象的区别是,它拦截了对象的操作。它提供了另外一种操作对象的方式,也就是增加了拦截功能。

好像有点废话,plainObj != proxyObj,但是因为对象是引用的存在,所以对代理对象的任何操作也就是对它所代理的普通对象的操作。代理对象也能解构,就像解构它所代理的普通对象一样,这也很好理解,解构就像读取对象的属性赋值给变量,代理对象理所当然的可以读取属性。

实际上,我们可以把 plainObjproxyObj 看做是同一个对象,但是因为 proxyObj 有一种特殊的能力,我们对 proxyObj 的所有操作都具有特殊的能力。而一旦解构了 proxyObj,生成了一些变量,那些变量自然不具有那些特殊的能力(有特殊能力的只是 proxyObj 本身)。

ref unwrapping ?

ref 在 setup 中需要写 .value 获取值,为什么在 template 又自动 unwrapping?

然而 unwrapping 只发生在 ref 变量为 template 的顶层属性时,为什么?

又,当一个 ref 是文本插值的最终求值时,又会 unwrapping。

也就是:

const object = { foo: ref(1) }
// 这样是不可以的
{{ object.foo + 1 }}
// 这样是可以的
{{ object.foo }}

为什么这么混乱呢?

以及,Ref Unwrapping in Reactive Objects 和 Ref Unwrapping in Arrays and Collections,又不一样。ref 作为 reactive 的属性,会自动 unwrapping,而 ref 在数组中和 Set、Map 中又不会。

computed set 的意义?

computed Best Practices 说:

Avoid mutating computed value#

The returned value from a computed property is derived state. Think of it as a temporary snapshot - every time the source state changes, a new snapshot is created. It does not make sense to mutate a snapshot, so a computed return value should be treated as read-only and never be mutated - instead, update the source state it depends on to trigger new computations.

Vue 官方文档说不建议修改 computed 值,又提供 set 的方式创建可修改的 computed,到底是什么意思?

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

这和 computed 的原意也是违背的,computed 是根据一个值计算另外一个值,也就是 A --> B,现在通过 set 可以做到 B--> A。从单向关系变为双向关系。

如何 watch props ?

Vue2 监听 props 时:

watch: {
  xxx: { // props 属性名

  }
}

直接对 props 属性进行监听,里面进行配置。

Vue3 很容易写成:

watch(props.xxx,
  (newVal, oldVal) => {
    // console.log(newVal, oldVal)
  }
)

这种写法不生效,看文档,watch source types 可以是 ref、reactive 等,但是 props 不是 reactive 对象吗?打印 props 确实是 Proxy 类型,问题是当 reactive 对象解构或者作为函数参数传递的时候,就失去了响应式连接。参考:vuejs.org/guide/essen…

所以 props.xxx 作为 watch 参数的时候,就是一个 plain object,如何解决呢?watch source types 还有一种是 a getter function,所以这样写就可以了:

watch(
  () => props.xxx,
  (newVal, oldVal) => {
    // console.log(newVal, oldVal)
  }
)

当然还有其他的解决方法,例如将 props.xxx 转为 ref。

参考:stackoverflow.com/questions/5…