setup函数
官方文档
setup函数的参数,主要有两个:props和context
- props: 父组件传过来的属性会被放到props对象中,如果需要在setup函数中使用,可通过props参数获取,规则和vue2一致。
- context,包含三个参数:
- attrs: 所有的非prop的attribute
- slots: 父组件传递过来的插槽(这个在渲染函数返回时会有用)
- emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过this.$emit发出事件)
<template>
<div>
<!-- template会自动将ref解包 -->
<h1>{{count}}</h1>
<button @click="increase">+1</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let count = ref(0);
const increase = () => {
console.log(count);
count.value++
}
return {
count,
increase
}
}
}
</script>
响应式API
reactive()
返回一个对象的响应式代理,通常用于复杂类型,比如:对象、数组。如果用于基本类型,会发出警告。
为什么使用reactive会变成响应式呢?
原因:当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集。当数据发生改变时,所以收集到的依赖都是进行对应的响应式操作。事实上,我们编写的data属性,也是在内部交给了reactive函数将其编程响应式对象的。
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
// 会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
ref()
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。用常用于基本类型,比如:String、Number、Boolean
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
readonly()
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
详细信息: 只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用来做响应性追踪
console.log(copy.count)
})
// 更改源属性会触发其依赖的侦听器
original.count++
// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!
toRefs
官方文档
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
如果我们使用ES6的结构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改解构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:
const state = reactive({
name: "张三",
age: 18
});
const { name, age } = state;
那么有没有办法让我们解构出来的属性是响应式的呢?
Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref,那么我们再次进行解构出来的name和age本身都是ref的:
// 此时,name和age都是响应式的
const { name, age } = toRefs(state);
然后修改name或者state.name中一个,另一个也会随着发生改变。
computed
官方文档
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
ref模板引用
官方文档
在vue2.x中使用模板引用通过this.$refs.xxx进行获取,但是vue3.x的组合式API并没有this,那么该怎么使用模板引用呢?
通过ref()。
生命周期函数
vue3.x组合式API中,生命周期函数的用法都差不多,都是注册一个回调函数,在组件挂载完成后执行;
举个例子:
function onMounted(callback: () => void): void
Provide/Inject函数
provide
provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 提供静态值
provide('foo', 'bar')
// 提供响应式的值
const count = ref(0)
provide('count', count)
// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>
inject
注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。
第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默认方式
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())
// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>
watch
官方文档
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
一个函数,返回一个值
一个 ref
一个响应式对象
…或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
watchEffect
官方文档
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
const count = ref(0)
// 只要count发生变化,下面函数就会自动执行
// count = 10 就停止
// watchEffect(() => console.log(count.value))
const stop = watchEffect(() => {
console.log(count.value);
if (stop > 10) {
stop();
}
})
// -> 输出 0
count.value++
// -> 输出 1
defineProps() 和 defineEmits()
为了在声明 props 和 emits 选项时获得完整的类型推导支持,我们可以使用 defineProps 和 defineEmits API,它们将自动地在
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup 代码
// emit('change',...args);
</script>
- defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
- defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
- defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推导。
- 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。
defineExpose
官方文档
使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
// 父组件就可以通过ref模板引用获取了(方法也是这样)
xxxRef.value.a;