# 组合式 API
作用:将逻辑关注点相同的代码收集到一起去。
# 全新的组合式勾子:setup
setup
中无法使用this
: 因为setup
发生在data
、computed
或methods
被解析之前。因此
setup
中只能访问到props
、attrs
、slots
、emit
。
# 参数
setup
中接受 props
和 context
并将返回的内容暴露给组件的其他部分(计算属性、方法、生命周期钩子等等) 以及组件的模板。
其中
props
是响应式的,所以不能使用ES6
的解构赋值,如要解构则需使用torefs(props)
来包裹。context
是一个普通 JavaScript 对象,暴露了其它可能在setup
中有用的值:
attribute
是元素标签的属性,property 是元素对象的属性input 的 value
attribute
是通过标签里的value=“test value”
定义的,可以通过input.getAttribute(‘value’)
获取,可以通过input.setAttribute(‘value’, ‘New Value’)
更新input
的value
property
可通过input.value
获取和更新,初始值是与attribute
中的赋值一致的
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数),可以解决返回渲染函数时无法return返回其他内容的问题
console.log(context.expose)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 带 ref 的响应式变量
# ref
使通过值传递的 string
和 number
类型也能变得响应式。
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
2
3
4
5
解析$ref
//html
<div ref='divRef'>
myDiv
</div>
//js
const divRef=ref(null)
console.log(divRef.value.innerHtml) //'myDiv'
2
3
4
5
6
7
8
# reactive
用法与 ref
类似,不同的是ref
用于基本数据类型,而reactive
是用于复杂数据类型,比如对象和数组。
reactive
将解包所有深层的refs
同时维持ref
的响应性。
readonly
接受一个对象 (响应式或纯对象) 或 ref
并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用于响应性追踪
console.log(copy.count)
})
// 变更 original 会触发依赖于副本的侦听器
original.count++
// 变更副本将失败并导致警告
copy.count++ // 警告!
2
3
4
5
6
7
8
9
10
11
12
13
14
shallowReactive
: 浅层次的数据改变,DOM会响应其变化;深层次的数据改变,DOM 不会随之刷新。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
2
3
4
5
6
7
8
9
10
11
12
# toRef
为源响应式对象上的某个 property 新创建一个 ref
(opens new window)。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
2
3
4
5
6
7
8
9
10
11
12
可对props
的响应式封装,作用为使本身具有响应式特点的 props
可以使用解构赋值。
import { ref, onMounted, watch, toRef } from 'vue'
setup (props) {
// 使用 `toRefs` 创建对 `props` 中的 `user` property 的响应式引用
const { user } = toRef(props)
}
2
3
4
5
# toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref
(opens new window)。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ref 类型声明
有时我们可能需要为 ref 的内部值指定复杂类型。可以在调用 ref
时传递一个泛型参数以覆盖默认推断,从而简洁地做到这一点:
const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number>
//或 const foo:Ref<string | number>=ref('foo')
foo.value = 123 // ok!
2
3
关于 Ref
如果泛型的类型未知,则建议将 ref
转换为 Ref<T>
是
interface Ref<T> {
value: T
}
function ref<T>(value: T): Ref<T>
2
3
4
5
function useState<State extends string>(initial: State) {
const state = ref(initial) as Ref<State> // state.value -> State extends string
return state
}
2
3
4
# shallowRef
创建一个跟踪自身 .value
变化的 ref,但不会使其值也变成响应式的。
# triggerRef
手动执行与 shallowRef
关联的任何作用 (effect)。
const foo = shallowRef({name:'true'})
// 改变 ref 的值是响应式的
foo.value = {name:'false'}//此时改变了
// 但是修改属性值时这个值不会被转换。
foo.value.name='true' // 还是 false
// 添加下面代码后进行强制转换
triggerRef(shallow) //变为 true 了
2
3
4
5
6
7
# markRaw
标记一个对象,使其永远不会转换为 proxy。返回对象本身。
- 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过 proxy 转换可以提高性能。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
2
3
4
5
6
# toRaw
返回 reactive
(opens new window) 或 readonly
(opens new window) 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
2
3
4
# setup 中的生命周期勾子
组合式 api
中的生命周期勾子与声明式一样,但前缀为 on
:即 onMounted
需要注意的是,
vue3
中将destoryed
重新命名为unmounted
;beforeDestroy
被修改为beforeUnmount
此外,因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在两个钩子中编写的任何代码都应该直接在setup
函数中编写。
// src/components/UserRepositories.vue `setup` function
<template>
<div>{{ value1 }}</div>
/* 在模板中使用setup的返回值时,refs被浅解析,因此不应使用.value*/
</template>
import { ref, onMounted } from 'vue'
// 在我们的组件中
setup (props) {
const value1 = ref([])
const fn = () => {
value1.value = 2
}
onMounted(fn) // 在 `mounted` 时调用函数 `fn`
return {
value1,
fn
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 监听属性 watch
其接受三个参数
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调
- 可选的配置选项
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
2
3
4
5
6
# watchEffect
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
2
3
4
5
6
7
8
9
watch
与 watchEffect
在手动停止侦听、清除副作用 (将 onInvalidate
作为第三个参数传递给回调)、刷新时机和调试方面有相同的行为。
const id =ref<string>('001')
const stop=watchEffect(onInvalidate=>{//此处将监听函数返回 stop
console.log(id,value)
onInvalidate(() => {
arr.clear()
})
})
stop()//当stop被调用时,stop所代表的监听函数终止监听
2
3
4
5
6
7
8
# 侦听器调试
onTrack
和 onTrigger
选项可用于调试侦听器的行为。
onTrack
将在响应式 property 或 ref 作为依赖项被追踪时被调用。onTrigger
将在依赖项变更导致副作用被触发时被调用。
这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger
语句来检查依赖关系:
watchEffect(
() => {
/* 副作用 */
},
{
onTrigger(e) {
debugger
}
}
)
2
3
4
5
6
7
8
9
10
onTrack
和 onTrigger
只能在开发模式下工作。
# 独立的 computed
属性
如watch、ref,通过导入属性可以实现在vue组件外部使用
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
2
3
4
# 渲染函数
expose
:使 return
渲染函数时,同时对外可以暴露自定义 property
import { h, ref, reactive } from 'vue'
export default {
setup() {
const readersNumber = ref(0)
const book = reactive({ title: 'Vue 3 Guide' })//复杂数据的ref封装
const count = ref(0)
const increment = () => ++count.value
expose({//借助expose,类似于 return ,传递出 increment 对象
increment
})
// 请注意这里我们需要显式使用 ref 的 value
return () => h('div', [readersNumber.value, book.title,count.value])
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Provide Inject
# 使用 provide
setup
中的 provide
函数允许你通过两个参数定义 property:
- name (
<String>
类型) - value
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
//或是进行响应式包装,使子孙组件能够响应父组件的改变
//const location = ref('North Pole')
//const geolocation = reactive({
// longitude: 90,
// latitude: 135
//})
//provide('location', location)
//provide('geolocation', geolocation)
//return {
// location
//}
},
methods: {
updateLocation() {//此处修改 provide 数据
this.location = 'South Pole'
}
}
}
</script>
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
27
28
29
30
31
32
33
34
35
36
37
建议:如果要对响应式的 provide / inject 进行修改时,尽可能的在定义的组件内修改。
如果需要在接收数据的组件内更新 inject 数据,可以类似于组件通讯一般,父组件将修改方法传递过去,然后在子组件内调用。
# 使用 Inject
inject
函数有两个参数:
inject
的property
的name
- 默认值 (可选),如未取到第二个值,则给其一个默认值
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')//对inject进行重构
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 子组件内更新 inject
的情况
推荐对 property 使用
readonly
确保通过provide
传递的数据不会被 inject 的组件更改。
<!-- src/components/MyMap.vue (祖先组件)-->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
provide('location', readonly(location))//使用了readonly
provide('geolocation', readonly(geolocation))
provide('updateLocation', updateLocation)
}
}
</script>
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
27
28
29
30
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
const updateUserLocation = inject('updateLocation')
return {
userLocation,
userGeolocation,
updateUserLocation
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 侦听模板引用
与生命周期钩子的一个关键区别是,watch()
和 watchEffect()
在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。
若想确保模板引用与 DOM 保持同步,并引用正确的元素,应该用 flush: 'post'
选项来定义。
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
const root = ref(null)
watchEffect(() => {
console.log(root.value) // => <div>This is a root element</div>
},
{//若未加该选项,则上一行将输出 null
flush: 'post'
})
return {//传递出 root 后,被模板引用,于是该模板被定义给了 root 的值
root
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24