Vue 3.0 初探 - 组合式 API

7,118 阅读5分钟

Vue 3.0 中引入了一种新的代码编写方式,那就是 Composition API,这是有别于 Vue 2.0 Options API 的一种函数式 API。无需通过指定一长串选项来定义组件,Composition API允许用户像编写函数一样自由地组合逻辑和代码。那么我们接下来就一起来看看 Composition API 是啥东东?

什么是 Composition API?

组合式 API:一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑

我们看一个简单的范例

<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>

我们先来看下这段代码发生了啥?

import { reactive, computed } from 'vue'

Component API 是以函数的形式展示组件属性,所以第一步就是导入我们需要的函数。在我们的例子中,我们用 reactive 创建响应属性,用 computed 创建计算属性。

export default {
  setup() {
    // ...
    return {
      state,
      increment,
    }
  }

还有一个 setup 函数,setup 函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点,如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文,我们就可以在模板里使用对应的属性和方法。

为什么要引入 Composition API

在 Vue2 中我们采用 Options API 来写这个范例

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

<script>
export default {
  data() {
    return {
      count: 0
    };
  },

  computed: {
    double() {
      return this.count * 2;
    }
  },

  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>

那在 Vue2 中如果我们要复用这个逻辑,我们可以通过诸如 mixins、高阶组件或是无渲染组件(通过作用域插槽实现的) 的模式达成。

首先我们来看下 mixins 的方式

import CounterMixin from './mixins/counter'

export default {
  mixins: [CounterMixin]
}

mixins 存在的问题是

  • 渲染上下文中暴露的 property 来源不清晰。例如在阅读一个运用了多个 mixin 的模板时,很难看出某个 property 是从哪一个 mixin 中注入的。

  • 命名空间冲突。mixin 之间的 property 和方法可能有冲突。

那么我们来看下作用域插槽的方式:

<template>
  <Counter v-slot="{ count, increment }">
     {{ count }}
    <button @click="increment">Increment</button> 
  </Counter> 
</template>

有了scoped slots,我们就可以通过v-slot属性准确地知道我们可以访问哪些属性,这样就更容易理解代码了。这种方法的缺点是,我们只能在模板中访问,而且只能在Counter组件作用域中使用。

除此之外,高阶组件和无渲染组件需要额外的有状态的组件实例,从而使得性能有所损耗。

ok,是时候让 Composition API 登场了

function useCounter() {
  const state = reactive({
    count: 0,
    double: computed(() => state.count * 2),
  });
  function increment () { count.value++ }

  return {
    state,
    incrememt
  }
}

export default {
  setup () {
    const { state, increment } = useCounter()
    return {
      state,
      increment
    }
  }
}

更加优雅了,不是吗?相比较而言:

  • 暴露给模板的 property 来源十分清晰,因为它们都是被组合逻辑函数返回的值。
  • 不存在命名空间冲突,可以通过解构任意命名
  • 不再需要仅为逻辑复用而创建组件实例
  • 仅依赖它的参数和 Vue 全局导出的 API,而不是依赖其微妙的 this 上下文

除了方便逻辑提取与复用之外,Composition API 带给我们的实际上更多的是一种新的代码编写思维。

当要去理解一个组件时,我们更加关心的是“这个组件是要干什么” (即代码背后的意图) 而不是“这个组件用到了什么选项”。基于选项的 API 撰写出来的代码自然采用了后者的表述方式,然而对前者的表述并不好

Options API 选项的强行分离为展示背后的逻辑关注点设置了障碍。此外,在处理单个逻辑关注点时,我们必须不断地在选项代码块之间“跳转”,以找到与该关注点相关的部分。

比如上面的例子中,基于 Options API 的方式我们必须在 data、computed、methods 三个选项中跳转,来完成这段逻辑。而通过 Composition API 的方式我们把相同逻辑关注点的代码并列在一起,形成了一个独立的逻辑函数。

存在问题

当然 Composition API 的引入也存在一定的弊端。

组合式 API 在代码组织方面提供了更多的灵活性,但它也需要开发人员更多地自我克制来 “正确地完成它”,组合式 API 会让没有经验的人编写出面条代码。

在 Options API 中实际上形成了一种强制的约定:

  • props 里面设置接收参数
  • data 里面设置变量
  • computed 里面设置计算属性
  • watch 里面设置监听属性
  • methods 里面设置事件方法

你会发现 Options API 都约定了我们该在哪个位置做什么事,这在一定程度上也强制我们进行了代码分割。 现在用 Composition API,不再这么约定了,于是乎,代码组织非常灵活,如果作为一个新手,或者不深入思考的码农,那么在逻辑越来越复杂的情况下,setup 代码量越来越多,同样 setup 里面的 return 越来越复杂,势必会落入“面条代码”的斡旋之中。

我们期望的是 setup() 函数现在只是简单地作为调用所有组合函数的入口

export default {
  setup() {
    // 网络状态
    const { networkState } = useNetworkState()

    // 文件夹状态
    const { folders, currentFolderData } = useCurrentFolderData(networkState)
    const folderNavigation = useFolderNavigation({
      networkState,
      currentFolderData,
    })
    const { favoriteFolders, toggleFavorite } = useFavoriteFolders(
      currentFolderData
    )
    const { showHiddenFolders } = useHiddenFolders()
    const createFolder = useCreateFolder(folderNavigation.openFolder)

    // 当前工作目录
    resetCwdOnLeave()
    const { updateOnCwdChanged } = useCwdUtils()

    // 实用工具
    const { slicePath } = usePathUtils()

    return {
      networkState,
      folders,
      currentFolderData,
      folderNavigation,
      favoriteFolders,
      toggleFavorite,
      showHiddenFolders,
      createFolder,
      updateOnCwdChanged,
      slicePath,
    }
  },
}

当然针对这些,官方也有一定的接纳策略,Vue 3.0 里 Composition API 并不是默认的方案,而是沿用 Vue 2.X 的 Options API, Composition API 将被定为为高级特性,因为它旨在解决的问题主要出现在大型应用程序中

参考资料:


欢迎关注微信公众号