Vue3 带来最大的革新莫过于选项式API (Composition API), 其中一大特性就是组合式函数。在 Vue 生态中,composables 一般翻译成 “组合式函数” 或 “可组合函数”, 意思是把一段逻辑(通常是响应式状态、计算属性、侦听器等)封装成一个独立的函数,这个函数可以在多个组件中复用, 也可以用于解耦臃肿的业务逻辑。
作用:逻辑复用、代码组织更清晰,不依赖组件上下文
一个简单的组合式函数样例
创建 useGrid.ts 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { computed, ref } from "vue"
type Grid = { columnId: number, rowId: number, resolution: number }
export const useGrid = () => { const columnId = ref<number>() const rowId = ref<number>() const resolution = ref<number>() const selectedGrid = ref<Grid>()
const getAllGrid = () => { ... }
return { selectedGrid, getAllGrid } }
|
在你的代码中应用
1 2 3 4 5 6 7 8 9 10 11
| <template> <div> 一个简单的组合式函数样例🚀 <span>当前选中的格网:{{ selectedGrid }}</span> </div> </template>
<script setup lang="ts"> import { useGrid } from "./useGrid.ts" const { selectedGrid, getAllGrid } = useGrid() </script>
|
等同于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <div> 一个简单的非组合式函数样例 <span>当前选中的格网:{{ selectedGrid }}</span> </div> </template>
<script setup lang="ts"> import { computed, ref } from "vue"
type Grid = { columnId: number, rowId: number, resolution: number }
const columnId = ref<number>() const rowId = ref<number>() const resolution = ref<number>() const selectedGrid = ref<Grid>()
const getAllGrid = () => { ... } </script>
|
组合式函数的好处
大家可以发现,组合式函数能够将你的业务逻辑从原本臃肿的代码中抽象解放出来!以便于逻辑复用,同时代码组织更加清晰,不依赖组件上下文
组合式函数的“陷阱”
选项式 API 作用域范围混淆——案例1
大家可以思考一下以下两种写法有什么不同?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { computed, ref } from "vue"
export const useGrid = () => { const columnId = ref<number>(0)
return { columnId } }
import { useGrid } from "./useGrid.ts" const { columnId: columnIdA } = useGrid() const { columnId: columnIdB } = useGrid()
columnIdA.value ++ columnIdB.value ++
console.log(columnIdA.value) console.log(columnIdB.value)
|
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { computed, ref } from "vue"
const columnId = ref<number>(0)
export const useGrid = () => { return { columId } }
import { useGrid } from "./useGrid.ts" const { columnId: columnIdA } = useGrid() const { columnId: columnIdB } = useGrid()
columnIdA.value ++ columnIdB.value ++
console.log(columnIdA.value) console.log(columnIdB.value)
|
输出结果
为什么会造成这种差异呢?
因为写法1把columnId这个响应式变量作为了组合函数封装的一部分,而写法2则把columnId作为了全局变量。在写法1的情况下,利用useGrid声明的两个响应式变量是双胞胎兄弟,它们本质上是不同的个体!而写法2虽然也声明了两个响应式变量,但本质上两个响应式变量指向的是同一个变量。
这两种做法各有什么实际用处呢?
写法1更适用于“逻辑复用”,在不同的地方声明useGrid,也即在不同的业务场景应用组合式函数,他们需要的是独立的响应式变量,不需要共享给其他组件。
而写法2更适用于“共享”,在不同的地方声明useGrid,它们可以拿到同一份响应式变量,需要的是能在不同的业务场景中共享一份数据。
选项式API作用域范围混淆——案例2
前情提要:useI18n是一个只能作用于选项式API范围内的组合式函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export const useA = () => { const { t } = useI18n() ...
return { ... } }
import { useA } from './useA.ts' const { ... } = useA()
export const useB = () => { ... return { ... } }
|
vue组件测试
1 2 3 4 5 6 7 8
| <template> <div></div> </template>
<script setup lang="ts"> import { useB } from './useB.ts' const { ... } = useB() </script>
|
这是另一种作用域混淆错误,因为useA中useI18n只能在选项式API内声明,而useB中却在组合式函数外部声明useA,这本质上脱离了选项式API的作用域,故vue组件就会报错useI18n必须在选项式API内部声明。
注意规避循环依赖
有的时候我们喜欢混用各个组合式函数,组合式函数之间可能存在相互引用的变量,就会造成循环依赖,故在设计时就要将各个模块解耦开,另外推荐一种笔者推荐的写法,可以有效避免循环依赖:
笔者推荐的写法
1 2 3 4 5 6 7 8 9 10
| import { ref } from 'vue' export const commonVar = ref<number>(0)
import { commonVar } from './shared.ts' ...
import { commonVar } from './shared.ts' ...
|