目录
一、setup
setup 函数是 composition API(组合式 API) 的一个入口点
✦ 调用时机
先创建组件实例,再初始化 props,紧接着调用 setup 函数。从生命周期钩子视角来看,setup 函数会在 beforeCreate 钩子之前被调用
✦ 模版中使用
如果 setup 返回一个对象,则对象的属性将会被合并到组件模版的渲染上下文
<template>
<div>{{ count }} {{ object.foo }}</div>
</template>
<script lang="ts">
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const object = reactive({ foo: 'bar' })
// 暴露给模板
return {
count,
object,
}
},
}
</script>
注意:setup 返回的 ref 在模版中会自动解开,不需要写 .value 属性
✦ 渲染函数 / JSX 中使用
setup 也可以返回一个函数,函数中也能使用当前 setup 函数作用域中的响应式数据
import { h, ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const object = reactive({ foo: 'bar' })
return () => h('div', [count.value, object.foo])
},
}
✦ setup 参数
setup 函数接收 props 作为它的第一个参数
export default {
props: {
name: String,
},
setup(props) {
console.log(props.name)
},
}
props 对象是响应式的,watchEffect 或 watch 会观察和响应式 props 更新
export default {
props: {
name: String,
},
setup(props) {
watchEffect(() => {
console.log(`name is: ` + props.name)
})
},
}
不要解构 props 对象,那样会使其失去响应式
export default {
props: {
name: String,
},
setup({ name }) {
watchEffect(() => {
console.log(`name is: ` + name) // Will not be reactive!
})
},
}
setup 函数第二个参数提供了一个上下文对象 context
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.emit
},
}
attrs 和 slots 都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值。可以解构无需担心后面访问到过期的值
const MyComponent = {
setup(props, { attrs }) {
// 一个可能之后回调用的签名
function onClick() {
console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性
}
},
}
✦ this 用法
this 在 setup 函数中不可用
✦ 类型定义
interface Data {
[key: string]: unknown
}
interface SetupContext {
attrs: Data
slots: Slots
emit: (event: string, ...args: unknown[]) => void // void 表示一个函数不需要返回值
}
function setup(props: Data, context: SetupContext): Data
为了获得传递给
setup()
参数的类型推断,需要使用 defineComponent (定义组件)方法
二、ref 传入原始数据类型
ref 是一个函数,它接受一个参数,返回一个神奇的响应式对象
接受一个参数值并返回一个响应式且可改变的 ref 对象
ref 对象拥有一个指向内部值的单一属性 .value
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
✦ 模版中访问
当 ref 在 setup() 返回的对象中,并在模板中使用时,它会自动解套,无需在模板内额外书写 .value
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
return {
count: ref(0),
}
},
}
</script>
✦ 作为响应式对象的属性访问
当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性
const count = ref(0)
const state = reactive({
count,
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
注意当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套
const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)
const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
✦ 类型定义
interface Ref<T> {
value: T
}
function ref<T>(value: T): Ref<T>
有时我们可能需要为 ref 做一个较为复杂的类型标注。我们可以通过在调用 ref 时传递泛型参数来覆盖默认推导:
const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number>
foo.value = 123 // 能够通过!
✦ 案例
<template>
<section>
<p>{{count}}</p>
<p>{{double}}</p>
<button @click="increase">👍 +1</button>
</section>
</template>
<script lang="ts">
import { computed, ref } from 'vue'
export default {
name: 'App',
setup() {
/**
* ref 是一个函数,它接受一个参数,返回一个神奇的响应式对象
* 初始化的 0 作为参数包裹到这个对象中去,在未来可以检测到改变并作出对应的相应
*/
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increase = () => {
count.value++
}
return {
count,
increase,
double
}
}
}
</script>
三、reactive 传入对象类型
接收一个普通对象然后返回该普通对象的响应式代理
const obj = reactive({ count: 0 })
✦ 类型定义
function reactive<T extends object>(raw: T): T
✦ 案例
<template>
<section>
<p>{{count}}</p>
<p>{{double}}</p>
<button @click="increase">👍 +1</button>
<ul>
<li v-for="item in numbers" :key="item">{{item}}</li>
</ul>
<p>{{person.name}}</p>
</section>
</template>
<script lang="ts">
import { computed, reactive, toRefs } from 'vue'
// interface 接口中的语句用分号分隔
interface DataProps {
count: number; // Expected a semicolon 需要一个分号
double: number;
increase: () => void;
numbers: number[];
person: { name?: string };
}
export default {
name: 'App',
setup() {
// 定义 data 数据类型
const data: DataProps = reactive({
count: 0,
double: computed(() => {
return data.count * 2
}),
increase: () => {
data.count++
},
numbers: [1, 2, 3],
person: {},
})
// 修改数组或对象中的值
data.numbers[0] = 11
data.numbers.push(4)
data.person.name = 'viking'
// toRefs API 保证 reactive 对象属性保持响应性
const refData = toRefs(data)
console.log('refData', refData)
console.log('refData.count', refData.count)
return {
// 非响应式数据 (property) DataProps.count: number
// count: data.count,
// double: data.double,
// increase: data.increase,
// 响应式数据 (property) count: Ref<number>
// count: refData.count,
// double: refData.double,
// increase: refData.increase,
...refData // 使用 spread 扩展运算符
}
}
}
</script>
✦ 使用 ref 还是 reactive 可以使用以下准则:
1) 使用 ref 传入原始类型,使用 reactive 传入对象类型
2) 所有场景都使用 reactive,但一定要记得使用 toRefs API 保证 reactive 对象属性保持响应性
四、生命周期钩子函数
可以直接导入 onXXX 一族的函数来注册生命周期钩子
生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例
与 2.x 版本生命周期相对应的组合式 API
-> 使用beforeCreate
setup()
-> 使用created
setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount(unmount 卸载)
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured(captured 捕获)
Vue3.0 新增调试钩子函数,两个钩子函数都接收一个 DebuggerEvent
onRenderTracked(Tracked 跟踪)
onRenderTriggered(Triggered 触发)
<template>
<section>
<p>{{msg}}</p>
<button @click="handleClickTab">切换</button>
</section>
</template>
<script lang="ts">
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onUnmounted,
onUpdated,
reactive,
toRefs
} from 'vue'
interface DataProps {
msg: string;
handleClickTab: () => void;
}
export default {
name: 'App',
setup() {
const data: DataProps = reactive({
msg: 'Hello world',
handleClickTab: () => {
// data.msg = 'Bye world'
// 三元操作符语句:条件 ? 条件成立时执行的语句 : 条件不成立时执行的语句
data.msg = data.msg === 'Hello world' ? 'Bye world' : 'Hello world'
}
})
const refData = toRefs(data)
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
onUnmounted(() => {
console.log('onUnmounted')
})
onErrorCaptured(() => {
console.log('onErrorCaptured')
})
onRenderTracked((event) => {
console.log('onRenderTracked', event)
})
onRenderTriggered((event) => {
console.log('onRenderTriggered', event)
})
return {
...refData
}
}
}
</script>
参考文档:
https://www.jianshu.com/p/03862c7bf35adocument.title API
课程文档
http://docs.vikingship.xyz/typescript.html
https://shimo.im/docs/YT9cdpDcKKCWV3CX/read
API
https://thecatapi.com/
https://api.thecatapi.com/v1/images/search?limit=1
https://dog.ceo/dog-api/