首先要知道的一些方法,基础api。避免看不懂vue3。
1. reactive
返回对象的响应式副本,实际上reactive 将解包所有深层的 refs,同时维持 ref 的响应性。
个人理解:相当于把reactive里面的内容a,push到一个对象中,而且还互相相应。我称这个对象为依赖于a的副本。内容为空,后续通过 obj.count = count 这样的写法,也能将其绑定响应。
const obj = reactive({ count: 0 })
响应式转换是“深层”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。
类型声明:
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
例子:
1 |
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 分配给 reactive property 时,ref 将被自动解包。
1 |
const count = ref(1) const obj = reactive({}) obj.count = count console.log(obj.count) // 1 console.log(obj.count === count.value) // true |
2. readonly
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
和reactive一样也是push给一个对象,但是他这个对象是不能改变它的值的。它本身是可以改变的,而且如果给这个对象设置watch了的话,还会触发它的监听器。
1 |
const original = reactive({ count: 0 }) const copy = readonly(original) watchEffect(() => { // 用于响应性追踪 console.log(copy.count) }) // 变更 original 会触发依赖于副本的侦听器 original.count++ // 变更副本将失败并导致警告 copy.count++ // 警告! 与 reactive 一样,如果任何 property 使用了 ref,当它通过代理访问时,则被自动解包: const raw = { count: ref(123) } const copy = readonly(raw) console.log(raw.count.value) // 123 console.log(copy.count) // 123 |
3. isProxy
检查对象是否是由 reactive 或 readonly 创建的 proxy。
4. isReactive
检查对象是否是由 reactive 创建的响应式代理
注意:从普通对象创建的只读 proxy,用isReactive检查会是false.如: const plain = readonly({ name: 'Mary' }) console.log(isReactive(plain)) // -> false
例子:
1 |
import { reactive, isReactive } from 'vue' export default { setup() { const state = reactive({ name: 'John' }) console.log(isReactive(state)) // -> true } } |
如果该代理是 readonly 创建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true。
1 |
import { reactive, isReactive, readonly } from 'vue' export default { setup() { const state = reactive({ name: 'John' }) // 从普通对象创建的只读 proxy const plain = readonly({ name: 'Mary' }) console.log(isReactive(plain)) // -> false // 从响应式 proxy 创建的只读 proxy const stateCopy = readonly(state) console.log(isReactive(stateCopy)) // -> true } } |
5. isReadonly
检查对象是否是由 readonly 创建的只读代理。
6. toRaw
返回 reactive 或 readonly 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。
不建议保留对原始对象的持久引用。请谨慎使用。例子:
1 |
const foo = {} const reactiveFoo = reactive(foo) console.log(toRaw(reactiveFoo) === foo) // true |
7. markRaw
标记一个对象,使其永远不会转换为 proxy。返回对象本身。
也就是它设置的值,对象,数组等。是不是响应的。
1 |
const foo = markRaw({}) console.log(isReactive(reactive(foo))) // false // 嵌套在其他响应式对象中时也可以使用 const bar = reactive({ foo }) console.log(isReactive(bar.foo)) // false |
markRaw 和下方的 shallowXXX API 使你可以有选择地退出默认的深度响应式/只读转换模式,并将原始的,未被代理的对象嵌入状态图中。它们可以根据情况灵活运用:
有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当渲染具有不可变数据源的大列表时,跳过 proxy 转换可以提高性能。
这些例子是进阶的运用,因为原始选择退出仅在根级别,因此,如果将嵌套在内的、未标记的原始对象添加进响应式对象,然后再次访问该响应式对象,就会得到原始对象被代理后的版本。这可能会导致同一性风险——即执行一个依赖于对象本身的操作,但同时使用同一对象的原始版本和被代理后的版本:
1 |
const foo = markRaw({ nested: {} }) const bar = reactive({ // 虽然 `foo` 被标记为原始,但 foo.nested 不是。 nested: foo.nested }) console.log(foo.nested === bar.nested) // false |
同一性风险通常很少见。然而,为了正确地使用这些 API,同时安全地避免同一性风险,就需要对响应性系统的工作原理有一个充分的理解。
8. shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
个人理解:类似浅拷贝。
1 |
const state = shallowReactive({ foo: 1, nested: { bar: 2 } }) // 改变 state 本身的性质是响应式的 state.foo++ // ...但是不转换嵌套对象 isReactive(state.nested) // false state.nested.bar++ // 非响应式 |
与 reactive 不同,任何使用 ref 的 property 都不会被代理自动解包。
9. shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。
1 |
const state = shallowReadonly({ foo: 1, nested: { bar: 2 } }) // 改变 state 本身的 property 将失败 state.foo++ // 失败 // ...但适用于嵌套对象 isReadonly(state.nested) // false state.nested.bar++ // 适用 |
与 readonly 不同,任何使用 ref 的 property 都不会被代理自动解包。
下面是主要使用的api
Refs
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
示例:
1 |
const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1 |
注意:如果将对象分配为 ref 值,则它将被 reactive 函数处理为深层的响应式对象。可以连着上面的reactive一起看着理解。
类型声明:
1 |
interface Ref<T> { value: T } function ref<T>(value: T): Ref<T> |
有时我们可能需要为 ref 的内部值指定复杂类型。可以在调用 ref 时传递一个泛型参数以覆盖默认推断,从而简洁地做到这一点:
1 |
const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number> foo.value = 123 // ok! |
如果泛型的类型未知,则建议将 ref 转换为 Ref<T>:
1 |
function useState<State extends string>(initial: State) { const state = ref(initial) as Ref<State> // state.value -> State extends string return state } |
Ref 解包
当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动浅层次解包内部值。只有访问嵌套的 ref 时需要在模板中添加 .value:
1 |
<template> <div> <span>{<!-- -->{ count }}</span> <button @click="count ++">Increment count</button> <button @click="nested.count.value ++">Nested Increment count</button> </div> </template> <script> import { ref } from 'vue' export default { setup() { const count = ref(0) return { count, nested: { count } } } } </script> |
如果你不想要访问实际的对象实例,可将其用 reactive 包裹:
1 |
nested: reactive({ count }) |
访问响应式对象
当 ref 作为响应式对象的 property 被访问或更改时,为使其行为类似于普通 property,它会自动解包内部值:
1 |
const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1 |
如果将新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref:
1 |
const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1 |
Ref 解包仅发生在被响应式 Object 嵌套的时候。当从 Array 或原生集合类型如 Map访问 ref 时,不会进行解包:
1 |
const books = reactive([ref('Vue 3 Guide')]) // 这里需要 .value console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) // 这里需要 .value console.log(map.get('count').value) |
响应式状态解构
当我们想使用大型响应式对象的一些 property 时,可能很想使用 ES6 解构来获取我们想要的 property:
1 |
import { reactive } from 'vue' const book = reactive({ author: 'Vue Team', year: '2020', title: 'Vue 3 Guide', description: 'You are reading this book right now ;)', price: 'free' }) let { author, title } = book |
遗憾的是,使用解构的两个 property 的响应性都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:
1 |
import { reactive, toRefs } from 'vue' const book = reactive({ author: 'Vue Team', year: '2020', title: 'Vue 3 Guide', description: 'You are reading this book right now ;)', price: 'free' }) let { author, title } = toRefs(book) title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref console.log(book.title) // 'Vue 3 Detailed Guide' |
isRef
检查值是否为一个 ref 对象。
unref
如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。
这个一般用在ts文件里面。
1 |
function useFoo(x: number | Ref<number>) { const unwrapped = unref(x) // unwrapped 现在一定是数字类型 } |
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
一般用来对某个对象里面的一个属性进行一个响应链接。
1 |
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 |
当你要将 prop 的 ref 传递给复合函数function时,toRef 很有用:
1 |
export default { setup(props) { useSomeFeature(toRef(props, 'foo')) // 把props继承的对象中的foo属性传入useSomeFeature这个函数。 } } |
即使源 property 不存在,toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用,可选 prop 并不会被 toRefs 处理。
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref
结合 ref对象 仅有一个 .value property,指向该内部值。根据原型链就能理解下面 ref 和原始 property 已经“链接”起来了 这句话了.
更简单理解toRef只能传一个,toRefs能传多个。
1 |
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 |
当从组合式函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:
1 |
function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) // 操作 state 的逻辑 // 返回时转换为ref return toRefs(state) } export default { setup() { // 可以在不失去响应性的情况下解构 const { foo, bar } = useFeatureX() return { foo, bar } } } |
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
使用自定义 ref 通过 v-model 实现 debounce 的示例:
1 |
<input v-model="text" /> function useDebouncedRef(value, delay = 200) { let timeout return customRef((track, trigger) => { return { get() { track() return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger() }, delay) } } }) } export default { setup() { return { text: useDebouncedRef('hello') } } } |
类型声明:
1 |
function customRef<T>(factory: CustomRefFactory<T>): Ref<T> type CustomRefFactory<T> = ( track: () => void, trigger: () => void ) => { get: () => T set: (value: T) => void } |
shallowRef
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。
1 |
const foo = shallowRef({}) // 改变 ref 的值是响应式的 foo.value = {} // 但是这个值不会被转换。 isReactive(foo.value) // false |
triggerRef
手动执行与 shallowRef 关联的任何作用 (effect)。
1 |
const shallow = shallowRef({ greet: 'Hello, world' }) // 第一次运行时记录一次 "Hello, world" watchEffect(() => { console.log(shallow.value.greet) }) // 这不会触发作用 (effect),因为 ref 是浅层的 shallow.value.greet = 'Hello, universe' // 记录 "Hello, universe" triggerRef(shallow) vue2中的创建变量 在3中写法是 props是你上一层组件穿过来的值,你可以自己在这接受,并且定义其类型。如: props: { user: { type: String, required: true } }, |
回归正题
1 |
setup(props){ const a = ref(0) } |
vue2在data里定义的a,在vue3中这么定义,ref里面定义其初始值。如:a:9, 在vue3写法就是const a = ref(9)watch的写法变成
1 |
setup(props){ const a = ref(0) watch(counter, (newValue, oldValue) => { console.log('The new counter value is: ' + counter.value) }) // 到最后要讲里面写的方法和变量return回来 return {a} } |
独立的 computed 属性
1 |
const twiceTheCounter = computed(() => counter.value * 2) return {twiceTheCounter} |
下面是最新总的写法
1 |
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted, watch, toRefs, computed } from 'vue' // in our component setup (props) { // 使用 `toRefs` 创建对 props 中的 `user` property 的响应式引用 const { user } = toRefs(props) const repositories = ref([]) const getUserRepositories = async () => { // 更新 `props.user ` 到 `user.value` 访问引用值 repositories.value = await fetchUserRepositories(user.value) } onMounted(getUserRepositories) // 在用户 prop 的响应式引用上设置一个侦听器 watch(user, getUserRepositories) const searchQuery = ref('') const repositoriesMatchingSearchQuery = computed(() => { return repositories.value.filter( repository => repository.name.includes(searchQuery.value) ) }) return { repositories, getUserRepositories, searchQuery, repositoriesMatchingSearchQuery } } |
使用 setup 函数时,它将接受两个参数:
props
context
setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。
例子:
1 |
export default { props: { title: String }, setup(props) { console.log(props.title) } } |
如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来完成此操作
const { title } = toRefs(props)
使用toRefs解绑后会安全消除 prop 的响应性。如:
const { title } = toRefs(props)
console.log(title.value)
context 是一个普通的 JavaScript 对象,它暴露三个组件的 property:
1 |
// MyBook.vue export default { setup(props, context) { // Attribute (非响应式对象) console.log(context.attrs) // 插槽 (非响应式对象) console.log(context.slots) // 触发事件 (方法) console.log(context.emit) } } |
它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。
1 |
// MyBook.vue export default { setup(props, { attrs, slots, emit }) { ... } } |
attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。
请注意,与 props 不同,attrs 和 slots 是非响应式的。
如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作
执行 setup 时,组件实例尚未被创建。因此,你只能访问以下 property:
props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
注意,从 setup 返回的 refs 在模板中访问时是被自动解开的,因此不应在模板中使用 .value
setup 还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:
1 |
// MyBook.vue import { h, ref, reactive } from 'vue' export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) // Please note that we need to explicitly expose ref value here return () => h('div', [readersNumber.value, book.title]) } } |
注册组件
1 |
Vue.component('button-counter', { data() { return { count: 0 } }, template: ` <button @click="count++"> Clicked {<!-- -->{ count }} times. </button> ` }) export default { render(h) { return h('button-counter') } } |
#3.x 语法
在 3.x 中,由于 VNode 是上下文无关的,不能再用字符串 ID 隐式查找已注册组件。取而代之的是,需要使用一个导入的 resolveComponent 方法:// 3.x
1 |
import { h, resolveComponent } from 'vue' export default { setup() { const ButtonCounter = resolveComponent('button-counter') return () => h(ButtonCounter) } } |
Vue 3 现在提供一个 emits 选项,和现有的 props 选项类似。这个选项可以用来定义一个组件可以向其父组件触发的事件。
在 Vue 2 中,你可以定义一个组件可接收的 prop,但是你无法声明它可以触发哪些事件:
1 |
<template> <div> <p>{<!-- -->{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'] } </script> |
vue3.的行为
和 prop 类似,现在可以通过 emits 选项来定义组件可触发的事件:
1 |
<template> <div> <p>{<!-- -->{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'], emits: ['accepted'] } </script> |
1 |
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" /> <!-- 是以下的简写: --> <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" :content="pageContent" @update:content="pageContent = $event" /> |
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用。
#3.x 语法
v-if 会拥有比 v-for 更高的优先级。
在 2.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么这个独立 attribute 总是会覆盖 object 中的绑定。
1 |
<!-- 模板 --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 结果 --> <div id="red"></div> |
在 3.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。
换句话说,相对于假设开发者总是希望独立 attribute 覆盖 object 中定义的内容,现在开发者能够对自己所希望的合并行为做更好的控制。
1 |
<!-- 模板 --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 结果 --> <div id="blue"></div> <!-- 模板 --> <div v-bind="{ id: 'blue' }" id="red"></div> <!-- 结果 --> <div id="red"></div> |
script setup是vue3中新引入的语法糖,目的是简化使用Composition API时冗长的模板代码。
如:
1 |
<script lang="ts"> import { defineComponent, ref } from 'vue' import { MyButton } from '@/components' export default defineComponent({ components: { MyButton }, props: { name: String }, setup() { const count = ref(0) const inc = () => count.value++ return { count, inc, } }, }) <script> |
当我们需要引入一个components时,不仅需要在文件头部显式import进行导入,而且需要components字段加入声明。
不仅如此,在setup中声明的变量如果需要被模板使用,那么需要在setup的尾部显式return返回,如果你的组件模板使用的变量不多,还可以勉强接受。但是当你的变量和方法逐渐增加时,每次都要在setup后进行return返回,这无疑是一件枯燥的事情,在重构代码时,你也会面临巨大挑战。
为了解决这个问题,vue3添加了script setup语法糖提案。
像上面这段代码,使用script setup语法糖重构后,将变成:
1 |
<script setup lang="ts"> import { defineComponent, ref, defineProps } from 'vue' import { MyButton } from '@/components' defineProps<{ name:string }>() const count = ref(0) const inc = () => count.value++ <script> |
基本用法
若要使用script setup语法,只需在原vue文件中的script标签加入setup属性。
<script setup lang="ts">
<script>
使用后意味着,script标签内的内容相当于原本组件声明中setup()的函数体,不过也有一定的区别。
使用setup中的参数
<script setup="props, context" lang="ts">
<script>
像这样,只要在setup处声明即可自动导入,同时也支持解构语法:
<script setup="props, { emit }" lang="ts">
<script>
还有一种写法 每次return的时候,结合三点运算符实现
setup() {
const state = reactive({
listParam: {
searchKey: "",
mainFollower: ""
}
})
watch([() => state.listParam.customerName, () => state.listParam.mainFollower],
([newCustomerName, newMainFoller],[oldCustomerName,oldMainFoller]) => {
state.listParam.customerName = newCustomerName.trim()
state.listParam.mainFollower = newMainFoller.trim()
},{
immediate: true
})
return {
…toRefs(state) // 三点运算符实现多个返回
}
}
await语法支持
在script setup内可以直接使用await语法:
1 |
<script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script> |
computed
接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
1 |
const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 错误 |
或者,接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。
1 |
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 |
类型声明:
1 |
// 只读的 function computed<T>( getter: () => T, debuggerOptions?: DebuggerOptions ): Readonly<Ref<Readonly<T>>> // 可写的 function computed<T>( options: { get: () => T set: (value: T) => void }, debuggerOptions?: DebuggerOptions ): Ref<T> interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void } interface DebuggerEvent { effect: ReactiveEffect target: any type: OperationTypes key: string | symbol | undefined } |
用ts的泛类,你可以自己定义computed的类型
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
1 |
const count = ref(0) watchEffect(() => console.log(count.value)) // -> logs 0 setTimeout(() => { count.value++ // -> logs 1 }, 100) |
使用watchEffect监听数据,可以单个或多个,不需要传入监听的数据源,而是直接执行里面的方法,获取到更新后的值。
1 |
vue文件 <el-form :model="listParam" label-position="left" inline> <el-form-item prop="searchKey"> <el-input v-model="listParam.searchKey" placeholder="请输入关键字" clearable size="small"></el-input> </el-form-item> <el-form-item prop="mainFollower"> <el-input v-model="listParam.mainFollower" placeholder="请输入跟进人姓名" clearable size="small"></el-input> </el-form-item> </el-form> |
js文件
1 |
setup() { const state = reactive({ listParam: { searchKey: "", mainFollower: "" } }) watchEffect(() => { state.listParam.searchKey = state.listParam.searchKey ? state.listParam.searchKey.trim() : "" state.listParam.mainFollower= state.listParam.mainFollower? state.listParam.mainFollower.trim() : "" }) return { ...toRefs(state) } } |
类型声明:
1 |
function watchEffect( effect: (onInvalidate: InvalidateCbRegistrator) => void, options?: WatchEffectOptions ): StopHandle interface WatchEffectOptions { flush?: 'pre' | 'post' | 'sync' // 默认:'pre' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void } interface DebuggerEvent { effect: ReactiveEffect target: any type: OperationTypes key: string | symbol | undefined } type InvalidateCbRegistrator = (invalidate: () => void) => void type StopHandle = () => void |
watch可以获取改变前后的值,watchEffect不可以
watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生变化时被调用。
与 watchEffect 相比,watch 允许我们:
惰性地执行副作用;
更具体地说明应触发侦听器重新运行的状态;
访问被侦听状态的先前值和当前值。
watch侦听单一源
侦听器数据源可以是一个具有返回值的 getter 函数,也可以直接是一个 ref:
1 |
// 侦听一个 getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // 直接侦听一个 ref const count = ref(0) watch(count, (count, prevCount) => { /* ... */ }) |
watch侦听多个源
侦听器还可以使用数组以同时侦听多个源:
1 |
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ }) |
[fooRef, barRef]这是你要监听的2个变量。
[foo, bar], [prevFoo, prevBar],这是2个改变前后监听到的值。
例子:
监听单一数据:
1 |
setup() { const state = reactive({ listParam: { searchKey: "" } }) watch(() => state.listParam.searchKey, (newVal,oldVal) => { console.log(newVal, oldVal) state.listParam.searchKey = newVal.trim() }) return { ...toRefs(state) } } |
监听多数据:
1 |
setup() { const state = reactive({ listParam: { searchKey: "", mainFollower: "" } }) watch([() => state.listParam.customerName, () => state.listParam.mainFollower], ([newCustomerName, newMainFoller],[oldCustomerName,oldMainFoller]) => { state.listParam.customerName = newCustomerName.trim() state.listParam.mainFollower = newMainFoller.trim() },{ immediate: true }) return { ...toRefs(state) } } |
与 watchEffect 相同的行为
watch 与 watchEffect 在手动停止侦听、清除副作用 (将 onInvalidate 作为第三个参数传递给回调)、刷新时机和调试方面有相同的行为。
类型声明:
1 |
// 侦听单一源 function watch<T>( source: WatcherSource<T>, callback: ( value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator ) => void, options?: WatchOptions ): StopHandle // 侦听多个源 function watch<T extends WatcherSource<unknown>[]>( sources: T callback: ( values: MapSources<T>, oldValues: MapSources<T>, onInvalidate: InvalidateCbRegistrator ) => void, options? : WatchOptions ): StopHandle type WatcherSource<T> = Ref<T> | (() => T) type MapSources<T> = { [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never } // 参见 `watchEffect` 共享选项的类型声明 interface WatchOptions extends WatchEffectOptions { immediate?: boolean // 默认:false deep?: boolean } |