关于 Vue 3 Composition API 的思考 - `reactive()` 被认为有害
Vue.js 以其直观的响应式设计脱颖而出。Vue 3 的 Composition API 将消除 Vue 2 中的一些限制,并提供更清晰的 API。
Composition API 简介
有两种方法可以创建反应性“事物”:
reactive()
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
这与 Vue 2 中的工作原理完全相同。data
只不过我们现在可以向它们添加新属性,因为在 Vue 3 中,反应性是通过代理实现的。
介绍Ref
Vue Composition API 引入了Ref
一个简单的对象,它只有一个属性.value
。我们可以使用 TypeScript 来表达:
interface Ref<A> {
value: A
}
有两种创建 ref 的方法:
ref()
.value
可以被获取/设置。
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)
*/
reactive()
不好;Ref
好。
本文的这一部分内容纯粹是我在使用 Composition API 构建了几个项目之后的一些初步想法。请自行尝试,如果您同意的话请告诉我。
在使用 Composition API 之前,我以为reactive()
它会是每个人都会用到的 API,因为它不需要做任何事.value
。令人惊讶的是,在用 Composition API 构建了几个项目之后,我一次都没用过reactive()
!
原因如下:
- 方便——
ref()
允许动态声明新的反应变量。 - 灵活性——
ref()
允许完全替换对象 - 明确性——
.value
迫使你意识到自己在做什么
1. 便利性
Composition API 的提出是为了提供一种方式,让代码能够根据其在组件中的功能(而非 Vue 中的功能)进行分组。Option API 将代码分组到data
、computed
、methods
、 生命周期等类别中。这使得按功能分组代码几乎不可能。参见下图:
请考虑以下示例:
const state = reactive({
count: 0,
errorMessage: null,
})
setTimeout(() => state.count++, 1000)
watch(state.count, count => {
if (count > 10) {
state.errorMessage = 'Larger than 10.'
}
})
如果我们用它来reactive()
存储多个属性,很容易陷入按功能而非特性分组的陷阱。你可能需要翻遍整个代码库来修改这个响应式对象。这会让开发过程变得不那么顺畅。
const count = ref(0)
setTimeout(() => count.value++, 1000)
const errorMessage = ref(null)
watch(count, count => {
if (count > 10) {
errorMessage.value = 'Larger than 10.'
}
})
另一方面,ref()
允许我们动态引入新变量。从上面的示例来看,我只在需要时才引入变量。这使得开发过程更加流畅和直观。
2.灵活性
我最初以为 的唯一目的ref()
是使原始值具有响应性。但它在ref()
与对象一起使用时也会变得非常方便。
考虑:
const blogPosts = ref([])
blogPosts.value = await fetchBlogPosts()
如果我们希望对 做同样的事情reactive
,我们需要改变数组。
const blogPosts = reactive([])
for (const post of (await fetchBlogPosts())) {
blogPosts.push(post)
}
或与我们的“挚爱”Array.prototype.splice()
const blogPosts = reactive([])
blogPosts.splice(0, 0, ...(await fetchBlogPosts()))
如图所示,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)
}
})
或者和我们最好的朋友splice
watch(page, page => {
blogPosts.splice(0, blogPosts.length, ...(await fetchBlogPostsOnPage(page)))
})
但如果我们用ref()
watch(page, page => {
blogPosts.value = await fetchBlogPostsOnPage(page)
})
使用起来非常灵活。
3.明确性
reactive()
返回一个对象,我们将与该对象进行交互,就像我们与其他非反应性对象交互一样。这很酷,但在实践中,如果我们处理其他非反应性对象,可能会变得令人困惑。
watch(() => {
if (human.name === 'Jason') {
if (!partner.age) {
partner.age = 30
}
}
})
我们无法真正判断human
或partner
是否是反应性的。但如果我们放弃 usingreactive()
并持续使用ref()
,就不会遇到同样的问题。
.value
乍一看可能有些冗长;但它有助于提醒我们,我们正在处理反应性问题。
watch(() => {
if (human.value.name === 'Jason') {
if (!partner.age) {
partner.age = 30
}
}
})
现在很明显,这human
是被动的,但不是partner
。
结论
以上观察和观点仅供参考。您怎么看?您同意ref()
Vue 3 会成为主流吗?或者您认为reactive()
它会成为首选?
请在评论区留言告诉我!我很想听听更多想法!
文章来源:https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c