说说Vue 3.0的Comparison Api的优势

615 阅读4分钟

Comparison Api是什么?

一组基于函数的附加API,允许灵活地组成组件逻辑。

a set of additive, function-based APIs that allow flexible composition of component logic.

单看官方解释也很苍白,接下来用个官方例子来说明一下:

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

上面例子中的reactivecomputed就都是Comparison Api提供的函数,用于给代码附加某项功能。 reactive 等价于 2.0版本的Vue.observable()computed跟2.0的计算属性一致。

在2.x里面,变量的响应式跟计算属性直接在组件作用域中可用;而3.0中,需要从vue中引入才能在组件中使用。

这涉及到另外一个概念:有状态组件无状态组件

有状态组件 vs 无状态组件

以上面的例子为例,把它改造为有状态组件(也就是2.x的写法):

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
export default {
  data() {
    return {
        state: {
            count: 0
        }
    };  
  },
  computed: {
    double: () => this.state.count * 2
  },
  methods: {
      increment() {
          this.state.count++
      }
  }
}
</script>

在有状态组件中,组件包含了this跟各种周期函数。而无状态组件,它是一个没有状态的组件,简单来说,就是没有this了,没有各种生命周期函数了。你需要用的时候,都从Comparison Api中去引入调用。

那这样做的好处是什么?

Comparison Api设计的动机

代码组织&逻辑复用

现有的api强制我们执行代码组织,让一切都变得不灵活。例如我想在上面的代码中添加一个显示用户登录状态的功能时,我只能在datacomputedmethods代码块中继续添加代码,随着功能的增长,复杂组件的代码变得越来越难以推理。

而且受制于执行代码组织的限制,使得在多个组件之间提取和重用逻辑的成本很高。

给出官方的一张图片,说明如果我们对这些逻辑问题中的每一个进行彩色编码,我们会注意到在使用组件选项表示它们时有多分散:

使用Comparison Api之前
正是这种碎片化使得难以理解和维护复杂的组件。通过选项的强制分隔使基本的逻辑问题变得模糊。在关注单个功能的时候,我们需要在页面上各种上下跳动代码。

接下来我们用Composition API来编写“创建新文件夹”功能进行比较:

function useCreateFolder (openFolder) {
  // originally data properties
  const showNewFolder = ref(false)
  const newFolderName = ref('')

  // originally computed property
  const newFolderValid = computed(() => isValidMultiName(newFolderName.value))

  // originally a method
  async function createFolder () {
    if (!newFolderValid.value) return
    const result = await mutate({
      mutation: FOLDER_CREATE,
      variables: {
        name: newFolderName.value
      }
    })
    openFolder(result.data.folderCreate.path)
    newFolderName.value = ''
    showNewFolder.value = false
  }

  return {
    showNewFolder,
    newFolderName,
    newFolderValid,
    createFolder
  }
}

在这个官方例子中,与“创建新文件夹”功能相关的所有逻辑都被封装在一个函数中,还可以通过具体的命名来描述功能。这种方式可以用来解耦组件的所有逻辑问题。

Options Api与Comparison Api的对比
现在,每个逻辑点的代码都放在了一起。当在大型组件上工作时,这大大减少了对上下“跳动”代码的需求。代码块也可以在编辑器中折叠,以使组件更易于扫描。

类型推导

开发人员在大型项目上的另一个常见功能要求是对TypeScript有更好的支持。Vue当前的API在与TypeScript集成时提出了一些挑战,这主要是因为Vue依赖单个this上下文来公开属性(例如在methods使用this是指向组件实例而不是methods对象的点后面的内部函数)。换句话说,Vue现有的API在设计时就没有考虑类型推断,在与TypeScript搭配时会产生很多复杂性。

而在3.0组件中使用的都是普通的变量和函数,不存在this上下文;用建议的API编写的代码可以享受完整的类型推断,几乎不需要手动类型提示。

总结

根据文档来说,从2.x升级到3.0开发个人觉得是比较平滑的,我觉得只要抓住两个重点去理解,1小时升级3.0开发不是梦。

  1. 3.0相比较2.x,它的组件中没有this上下文跟周期函数,所有这些都需要从Comparison Api中引入
  2. 2.x只能在页面组件中使用周期函数,3.0中所有封装的逻辑组件都可以从Comparison Api中引入周期函数进行调用。

参考资料