关于 Vue 3 Composition API 的思考 - `reactive()` 被认为有害

2025-06-07

关于 Vue 3 Composition API 的思考 - `reactive()` 被认为有害

Vue.js 以其直观的响应式设计脱颖而出。Vue 3 的 Composition API 将消除 Vue 2 中的一些限制,并提供更清晰的 API。

Composition API 简介

有两种方法可以创建反应性“事物”:

  1. reactive()
  2. ref()/computed()

介绍reactive()

reactive(obj)将返回一个与 完全相同的新对象obj,但新对象的任何变异都会被跟踪。

例如:

// template: {{ state.a }} - {{ state.b }}
const state = reactive({ a: 3 })
// renders: 3 - undefined

state.a = 5
state.b = 'bye'
// renders: 5 - bye
Enter fullscreen mode Exit fullscreen mode

这与 Vue 2 中的工作原理完全相同。data只不过我们现在可以向它们添加新属性,因为在 Vue 3 中,反应性是通过代理实现的。

介绍Ref

Vue Composition API 引入了Ref一个简单的对象,它只有一个属性.value。我们可以使用 TypeScript 来表达:

interface Ref<A> {
  value: A
}
Enter fullscreen mode Exit fullscreen mode

有两种创建 ref 的方法:

  1. ref()
    • .value可以被获取/设置。
  2. computed()
    • .value除非提供 setter,否则是只读的。

例如:

const countRef = ref(0) // { value: 0 }
const countPlusOneRef = computed(() => countRef.value + 1) // { value: 1 }
countRef.value = 5

/*
 * countRef is { value: 5 }
 * countPlusOneRef is { value: 6 } (readonly)
 */
Enter fullscreen mode Exit fullscreen mode

reactive()不好;Ref好。

本文的这一部分内容纯粹是我在使用 Composition API 构建了几个项目之后的一些初步想法。请自行尝试,如果您同意的话请告诉我。

在使用 Composition API 之前,我以为reactive()它会是每个人都会用到的 API,因为它不需要做任何事.value。令人惊讶的是,在用 Composition API 构建了几个项目之后,我一次都没用过reactive()

原因如下:

  1. 方便——ref()允许动态声明新的反应变量。
  2. 灵活性——ref()允许完全替换对象
  3. 明确性——.value迫使你意识到自己在做什么

1. 便利性

Composition API 的提出是为了提供一种方式,让代码能够根据其在组件中的功能(而非 Vue 中的功能)进行分组。Option API 将代码分组到datacomputedmethods、 生命周期等类别中。这使得按功能分组代码几乎不可能。参见下图:

请考虑以下示例:

const state = reactive({
  count: 0,
  errorMessage: null,
})
setTimeout(() => state.count++, 1000)
watch(state.count, count => {
  if (count > 10) {
    state.errorMessage = 'Larger than 10.'
  }
})
Enter fullscreen mode Exit fullscreen mode

如果我们用它来reactive()存储多个属性,很容易陷入按功能而非特性分组的陷阱。你可能需要翻遍整个代码库来修改这个响应式对象。这会让开发过程变得不那么顺畅。

const count = ref(0)
setTimeout(() => count.value++, 1000)

const errorMessage = ref(null)
watch(count, count => {
  if (count > 10) {
    errorMessage.value = 'Larger than 10.'
  }
})
Enter fullscreen mode Exit fullscreen mode

另一方面,ref()允许我们动态引入新变量。从上面的示例来看,我只在需要时才引入变量。这使得开发过程更加流畅和直观。

2.灵活性

我最初以为 的唯一目的ref()是使原始值具有响应性。但它在ref()与对象一起使用时也会变得非常方便。

考虑:

const blogPosts = ref([])
blogPosts.value = await fetchBlogPosts()
Enter fullscreen mode Exit fullscreen mode

如果我们希望对 做同样的事情reactive,我们需要改变数组。

const blogPosts = reactive([])
for (const post of (await fetchBlogPosts())) {
  blogPosts.push(post)
}
Enter fullscreen mode Exit fullscreen mode

或与我们的“挚爱”Array.prototype.splice()

const blogPosts = reactive([])
blogPosts.splice(0, 0, ...(await fetchBlogPosts()))
Enter fullscreen mode Exit fullscreen mode

如图所示,ref()在这种情况下,操作起来更简单,因为你可以直接用新数组替换整个数组。如果这还不能说服你,想象一下如果blogPosts需要分页的话:

watch(page, page => {
  // remove everything from `blogPosts`
  while (blogPosts.length > 0) {
    blogPosts.pop()
  }

  // add everything from new page
  for (const post of (await fetchBlogPostsOnPage(page))) {
    blogPosts.push(post)
  }
})
Enter fullscreen mode Exit fullscreen mode

或者和我们最好的朋友splice

watch(page, page => {
  blogPosts.splice(0, blogPosts.length, ...(await fetchBlogPostsOnPage(page)))
})
Enter fullscreen mode Exit fullscreen mode

但如果我们用ref()

watch(page, page => {
  blogPosts.value = await fetchBlogPostsOnPage(page)
})
Enter fullscreen mode Exit fullscreen mode

使用起来非常灵活。

3.明确性

reactive()返回一个对象,我们将与该对象进行交互,就像我们与其他非反应性对象交互一样。这很酷,但在实践中,如果我们处理其他非反应性对象,可能会变得令人困惑。

watch(() => {
  if (human.name === 'Jason') {
    if (!partner.age) {
      partner.age = 30 
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

我们无法真正判断humanpartner是否是反应性的。但如果我们放弃 usingreactive()并持续使用ref(),就不会遇到同样的问题。

.value乍一看可能有些冗长;但它有助于提醒我们,我们正在处理反应性问题。

watch(() => {
  if (human.value.name === 'Jason') {
    if (!partner.age) {
      partner.age = 30 
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

现在很明显,这human是被动的,但不是partner

结论

以上观察和观点仅供参考。您怎么看?您同意ref()Vue 3 会成为主流吗?或者您认为reactive()它会成为首选?

请在评论区留言告诉我!我很想听听更多想法!

文章来源:https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c
PREV
无视图的 Vue - 无渲染组件简介
NEXT
看来 Vue.js 周末可能会超越 React!构建这个项目时值得一提的几点: