Vue3 组合式API初体验

一、背景

Vue3.x 版本的出现带来了许多令人眼前一亮的新特性,其中组合式 API(Composition API),一组附加的、基于功能的 API 被作为一种新的逻辑复用和代码组织的方式提供给了开发者,提供更加灵活的组合组件逻辑能力。

二、什么是组合式API(Composition API )

  • 组合式 API:一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑
  • 用一句通俗的话来说:composition API 其实是用于解决功能、数据和业务逻辑分散的问题,使项目更益于模块化开发以及后期维护

先看一段简单的代码:

<template>
  <h2>组合式API</h2>
  <el-button type="primary" @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </el-button>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

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

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

    function increment() {
      state.count++
    }

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

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

import {computed, reactive} from "vue";

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

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

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

组合式API全景

组合式 API 的使用,官方参考文档和 API 文档有详细说明,这里不过多说明。为方便大家理解和把握,根据官方的 API 文档整理了全景图。
在这里插入图片描述
Vue 组合式API 可以分为五大块:

  1. 数据响应(复杂对象):响应性基础API ,支持复杂对象 ObjectArray 的数据响应
  2. 数据响应(内部值):内部值指 JS内置的简单数据结构,包括 StringNumber
  3. computedwatch:基于响应式数据的数据计算与监听
  4. 生命周期:对原生命周期封装,例如:onMountedonBeforeMount
  5. 其他 API:重要支持性 API

为什么要引入组合式API

在 Vue2 中我们采用 Options API 来写上面的代码:

<template>
  <h2>Option API</h2>
  <el-button type="primary" @click="increment">
    Count is: {{ count }}, double is: {{ double }}
  </el-button>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

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

那在 Vue2 中如果我们要复用这个逻辑,我们可以通过诸如 mixins 或作用域插槽的模式达成。

mixins 的方式

<template>
  <h2>Option API Mixins</h2>
  <el-button type="primary" @click="increment">
    Count is: {{ count }}, double is: {{ double }}
  </el-button>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

<script>
import CounterMixin from '@/mixins/counter'
export default {
  mixins: [CounterMixin]
}
</script>

mixins 存在的问题是:

  1. 渲染上下文中暴露的 property 来源不清晰。例如在阅读一个运用了多个 mixin 的组件代码时,很难看出某个 property 是从哪一个 mixin 中注入的。
  2. 命名空间冲突。mixin 之间的 property 和方法可能有冲突。

域插槽的方式

<template>
  <h2>Option API Slot</h2>
  <Counter v-slot="{ count, double, increment }">
    <el-button type="primary" @click="increment">
      Count is: {{ count }}, double is: {{ double }}
    </el-button>
  </Counter>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

<script>
import Counter from '@/components/Counter'
export default {
  components: {
    Counter
  }
}
</script>

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

组合式API的方式

useCounter.js

// 将这部分代码逻辑抽离出来
import {computed, reactive} from "vue"

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

  return {
    state,
    increment
  }
}

CounterSetup.vue

<template>
  <h2>组合式API script setup 语法糖</h2>
  <el-button type="primary" @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </el-button>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

<script setup>
import { useCounter } from '@/libs/useCounter'
const { state, increment } = useCounter()
</script>

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

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

结论

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

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

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

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

组合式API存在的问题

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

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

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

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

会发现 Options API 都约定了我们该在哪个位置做什么事,这在一定程度上也强制我们进行了代码分割。

现在用 Composition API,不再这么约定了,所以代码组织非常灵活,如果作为一个新手,或者不深入思考的码农,那么在逻辑越来越复杂的情况下,setup 代码量越来越多,同样 setup 里面的 return 越来越复杂,势必会落入“面条代码”地步。

组合式API 的 script setup 语法糖

<setup script> 就是vue3新出的一个语法糖,使用方法就是在 script 标签的后面加上一个 setup 修饰。

代码如下:

<template>
  <h2>组合式API script setup 语法糖</h2>
  <el-button type="primary" @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </el-button>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>

<script setup>
import { useCounter } from '@/libs/useCounter'
const { state, increment } = useCounter()
</script>

优点:

  1. 自动注册组件
  2. 属性和方法无需返回
  3. 更简洁的代码
  4. 更好的运行时性能

顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用:

<template>
  <div @click="log">{{ msg }}</div>
  <div>{{ capitalize('hello') }}</div>
</template>
<script setup>
// 引入
import { capitalize } from '@/libs/helpers'
// 变量
const msg = 'Hello!'
// 函数
function log() {
  console.log(msg)
}
</script>

使用 this

setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data propertycomputed propertymethods 被解析之前,所以它们无法在 setup 中被获取。这使得 setup 在和其它 Options API 一起使用时可能会导致混淆。

steup中的生命周期钩子(对比 Options API

在这里插入图片描述

扩展

vite项目实现vue3函数和组件库的自动按需导入

在一个vue3项目中,我们经常需要从 vuevue-router等引入相应的函数方法进单个文件中。

比如:

import {  ref,  reactive,  toRefs,  watchEffect,  computed,  watch,  onUnmounted,} from 'vue'
import { useRouter } from 'vue-router'

这时我们可以使用基于unplugin项目的两个插件:

  1. 首先在项目中安装这两个插件
yarn add unplugin-auto-import unplugin-vue-components -D
  1. 然后在 vite.config.js 中配置
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()], // 这里使用ElementPlus组件库
      imports: [
        'vue',
        'vue-router',
        {
          vuex: ['useStore']
        }
      ]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    }),
  ]
})

配置完成后我们就可以直接在项目中这样使用

<template>
  <!--el-button 无需从 Element Plus 中导入-->
  <el-button type="primary">{{ text }}</el-button>
  <br>
  <div style="display: block;margin-top: 50px;">
    <router-link to="/" style="margin-top: 50px">首页</router-link>
  </div>
</template>
<script setup>
  const text = ref('我是一个 Element Plus 按钮')  // ref 无需从vue中导入
</script>

项目地址

本文所有代码地址:
gitlab(需要注册):https://gitlab.explorexd.com/devs/vue3-demo
github:https://github.com/jiaxudonggit/vue3-demo

参考文档

https://v3.cn.vuejs.org/guide/introduction.html
https://juejin.cn/post/7027443242928979982
https://juejin.cn/post/6844904191928827912
https://juejin.cn/post/7025837638540066824
https://element-plus.gitee.io/zh-CN/guide/installation.html
https://cn.vitejs.dev/guide/#scaffolding-your-first-vite-project

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值