[译]关于vue3 compostion api "reactive()"方法的害处

785 阅读3分钟

Composition API简介

你可以通过下面两个api创建响应性对象:

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

reactive简介

reactive(obj) 会返回一个响应性对象,对象的所有属性都具备响应性。

例如:

// 模板: {{ state.a }} - {{ state.b }}
const state = reactive({ a: 3 })
// 渲染结果: 3 - undefined

state.a = 5
state.b = 'bye'
// 渲染结果: 5 - bye

他和vue2的data属性是一样的,但是他可以给对象添加新的属性,属性也具备响应性,因为vue3是基于proxy实现的。

Ref简介

Ref相当于一个包含.value属性的简单对象,就像下面Typescript的定义:

interface Ref<A> {
  value: A
}

两个方法创建 refs:

  1. ref()
    • .value 支持读取和赋值
  2. computed()
    • .value 只支持读取

例子:

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)
 */

推荐ref而非reactive

这只是我在使用compostion api实践总结的小小观点,并不具备权威性,你也可以试一下,告诉我你的观点。

在使用compostion api前,我以为reactive会是首选API,因为它不需要通过.value读取值。但是,我使用compostion api 一段时间后,我不再使用reactive

三个理由:

  1. 便利性 - ref() 允许自由的定义一个响应性变量。

  2. 灵活性 - ref() 允许替换整个对象

  3. 明确性 - .value 明确告诉你正在做什么

1. 便利性

composition api 提供一种以组件功能为维度组合逻辑的方法,他不同于options 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() 先定义所有属性,会导致像vue2那样,以特性划分代码,而不是根据逻辑划分,这会导致你的业务逻辑代码分散到不同地方。

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.灵活性

I initially thought the sole purpose of ref() was to enable primitive values to be reactive. But it can become extremely handy too when using ref() with objects.

我最初认为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()明显比较简单,因为你只需要把新的数组替换旧的数组即可。再看看下面这个分页例子:

watch(page, page => {
  // 删除所以内容
  while (blogPosts.length > 0) {
    blogPosts.pop()
  }

  // 添加新数据
  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 
    }
  }
})

上面的代码,你没法知道humanpartner哪个是响应对象,但是用ref()就不会有这个问题

.value 看起来有点啰嗦,但是这可以提醒你这是响应对象。

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

现在很显然human是响应性对象,partner不是

结论

上面只是我的初步看法,你的看法是怎样的,评论区见

原文地址:dev.to/ycmjason/th…