Vue3 组合式API

一、选项式 API 的缺点

在 Vue2 中,我们通过选项式 API 编写组件,分属于不同类别的内容需要填入对象的选项 API 之中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
···模板···
</template>

<script>
export default {
data() {
return {
···数据···
}
},
methods: {
···方法···
}
mounted() {
···钩子函数···
}
}
</script>

当页面中只有一到两个模块时,这种做法是合适的。

当页面中存在大量模块时,这种做法便开始暴露出问题:同一个模块的不同类别的代码需要分散于各个选项之中,同一个选项之中会被散乱放置分属于不同模块的代码。维护一个组件往往需要在多个选项之间来回跳转,并在各个选项中辨认属于该模块的代码。组件的阅读和维护因为选项 API 的代码组织方式成为难题。

二、组合式 API

为了解决选项式 API 的问题,Vue3 新增了组合式 API,它将不再需要将代码分门别类放置于各个选项之中,而是可以由程序员自行按模块进行划分。

三、setup

1. 说明

  • setup 是新增的选项式 API
  • setup 是组合式 API 的入口,使用组合式 API 的代码应该在 setup 选项中编写
  • setup 将在 beforeCreate 之前调用,因此无法访问组件实例(this)
  • setup 应该返回一个对象,对象中的属性和方法可以被组件的其它部分使用
  • setup 可以返回渲染函数

2. 参数 - props

(1) props

setup 可以接收的第一个参数是 props,它包含外部传入的 prop,它是响应式的,会在 prop 更新时更新。

(2) 获取 prop

如果希望获取 prop 的值,可以通过 props.prop名 获取。

如果希望获取 prop 且不丢失响应性,应该:

  • 通过 toRefs 获取一个 “普通” 对象,该对象中包含每个 prop 对应的 ref 对象

    1
    2
    3
    4
    setup(props) {
    let myProps = toRefs(props)
    ···
    }
  • 通过 toRef 获取指定 prop 对应的 ref 对象

    1
    2
    3
    4
    setup(props) {
    let prop名 = toRef(props, 'prop名')
    ···
    }

3. 参数 - context

setup 可以接收的第二个参数是 context,它是非响应式的,包含了组件的其它信息(attrs、slots、emit、expose)。

四、生命周期钩子

在组合式 API 中,如果要使用生命周期钩子,应该从 vue 处导入名为 on + 钩子名 的函数,调用并传入函数。

名称对照表如下:

选项式 API Hook inside setup
beforeCreate Not needed
created Not needed
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

值得注意的是,在组合式 API 中没有 beforeCreatecreated 对应的钩子函数,这是因为 setup 的调用时间与它们相近,因此应该将这两个生命周期中应发生的事情写到 setup 中。

五、provide/inject

可以将 provide/inject 看作可跨级的 prop,它用于向后代组件传递信息,provide 用于发送,inject 用于接收。

在组合式 API 中,provide 和 inject 需要显示导入,使用方式如下:

  • 祖先组件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import { provide } from 'vue'

    ···

    setup() {
    provide(名, 值)
    provide(名, 值)
    ···
    }
  • 后代组件:

    1
    2
    3
    4
    5
    6
    7
    import { inject } from 'vue'

    ···

    setup() {
    let ··· = inject(名, 可选的默认值)
    }

    默认值是可选的,如果没有获取到对应的值,则默认值会生效

在 Vue2 中,provide/inject 是没有响应性的;

在 Vue3 中,可以通过传递 “响应式数据” 来为 provide/inject 增加响应性。

六、响应式 ref 和 模板 ref

1. 什么是响应式 ref 和 模板 ref ?

在 Vue3 中,存在响应式 ref 和模板 ref 这两个概念,其中:

  • 响应式 ref:根据值生成一个 ref 对象,它是响应式的

    1
    2
    3
    4
    let refMsg = ref("Hello")

    // ref对象的改变会自动触发视图的更新
    resMsg.value = "Hello World!"
  • 模板 ref:模板 ref 是一种访问模板元素的方式

    1
    2
    3
    4
    5
    <p ref="test">123</p>

    ···

    this.$refs.test

2. 重名问题

你可能会产生这样的疑惑,一个是寻找模板元素,一个是根据值生成响应化对象,为什么要取相同的名字?

事实上,

  • 如果使用选项式 API,(一般情况下)无需使用响应式 ref,只使用模板 ref 即可
  • 如果使用组合式 API,响应式 ref 和模板 ref 的概念是统一的

3. 组合式 API 中的响应式 ref 和模板 ref

(1) 响应式 ref 的使用:

1
2
3
4
5
6
7
setup() {
let rValue = ref(value)

return {
rValue
}
}

(2) 模板 ref 的使用

1
2
3
4
5
6
7
8
9
10
11
<p ref="元素名">···</p>

···

setup() {
let 元素名 = ref(null)

return {
元素名
}
}

如果模板中 “元素的 ref 名” 与 setup 中 return 的 “ref 对象名” 相同,则两者会进行绑定,即 模板ref == ref对象。如果要访问元素,既可以通过 $refs.元素名 访问,也可以通过 ref对象.value 访问。

示例:

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
<template>
<p ref="myP">123</p>
</template>

<script>
import {ref} from "vue";

export default {
name: "TemplateRef",
setup() {
let myP = ref(null)
return {
myP
}
},
mounted() {
setInterval(() => {
// 在选项式API中访问ref,无需添加.value
console.log(this.myP)
console.log(this.$refs.myP)
console.log(this.myP === this.$refs.myP)
}, 1000)
}
}
</script>

由于模板 ref 会在模板渲染后被赋值,因此其初始值并不重要,一般设为 null

七、watchEffect

1. watchEffect()

1
2
3
watchEffect(() => {
···
})
  • watchEffect 是 Vue3 中新增的专用于组合式 API 的函数
  • 调用 watchEffect 时需要传入一个函数,函数中可以使用若干响应式数据
  • 它会立即执行一次函数
  • 它会自动 “追踪” 函数所使用的响应式数据,当它们变化时重新运行函数
1
2
3
4
5
6
7
8
9
10
11
let amount = ref(0)

let price = ref(10)

watchEffect(() => {
console.log("总价是:" + amount.value * price.value)
}) // "总价是:0"[初始化执行第一次]

amount++ // "总价是:10"[响应式对象改变,自动执行]

price++ // "总价是:11"[响应式对象改变,自动执行]

2. 回撤操作

watchEffect 传入的函数允许接收一个参数,该参数是一个回调函数,它将会在 watchEffect 下一次执行前被调用,可以用于回撤操作。

1
2
3
4
5
6
7
watchEffect((onCleanup) => {
// do something

onCleanup(() => {
// 回撤之前的操作
})
})

3. 停止监听

  • 默认情况下,watchEffect 将会随组件生命周期结束而停止监听

  • 如果希望手动监听,可以接收 watchEffect 的返回值并调用

    1
    2
    3
    4
    5
    const stop = watchEffect(() => {
    ...
    })

    stop()

4. watch 与 watchEffect

  • watch 需要明确指定要跟踪的响应式数据,并且只会跟踪它们
  • watchEffect 可以无需指定要跟踪的响应式数据,只需要说明希望做的事情,并在事情中使用响应式数据,Vue 会自动跟踪它们
  • watch 将监听的对象和希望做的事情分离,而 watchEffect 将它们耦合

参考