Vue3查缺补漏
Chao 工程师

v-show

v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。

当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行。

同时使用 v-if 和 v-for 是不推荐的,因为这样二者的优先级不明显。

v-for

同时使用 v-if 和 v-for 是不推荐的,因为这样二者的优先级不明显。

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。

这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名。

reactive()

创建一个响应式对象数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 })

function increment() {
state.count++
}
</script>

<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
局限性:
  1. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 stringnumber 和 boolean 这样的 原始类型 无效。
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。

这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

1
2
3
4
let state = reactive({ count: 0 })

// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })

同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const state = reactive({ count: 0 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++

// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count

ref()

Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>

<template>
<button @click="increment">
{{ count }} <!-- 无需 .value -->
</button>
</template>

computed()

计算属性值会基于其响应式依赖被缓存。

一个计算属性仅会在其响应式依赖更新时才重新计算。 只要 依赖 不改变,无论多少次访问 都会立即返回先前的计算结果,而不用重复执行 getter 函数

Getter 不应有副作用

不要在 getter 中做异步请求或者更改 DOM!

一个计算属性的声明中描述的是如何根据其他值派生一个值。因此 getter 的职责应该仅为计算和返回该值。

避免直接修改计算属性值

从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

watch()、watchEffect()

侦听数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})

// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})

注意,你不能直接侦听响应式对象的属性值。

需要用一个返回该属性的 getter 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = reactive({ count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})

// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
1
2
3
4
5
6
7
8
9
watch(todoId, async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
}, {
immediate: true, // 强制侦听器的回调立即执行
flush: 'post', // 侦听器回调中能访问被 Vue 更新之后的 DOM
})

watchEffect() 允许我们自动跟踪回调的响应式依赖。上面的侦听器可以重写为:

1
2
3
4
5
6
7
8
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
}, {
flush: 'post' // 后置刷新的watchEffect()有个更方便的别名watchPostEffect()
})

要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:

1
2
3
const unwatch = watchEffect(() => {})
// 当该侦听器不再需要时
unwatch()

需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:

1
2
3
4
5
6
7
8
// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})

ref

引用在元素上,则获取的是DOM元素;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
input.value.focus()
})
</script>

<template>
<input ref="input" />
</template>

引用在选项式API子组件上:被引用的组件实例和该子组件的 this 完全一致。这意味着父组件对子组件的每一个属性和方法都有完全的访问权。当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。

引用在组合式子组件上:父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
a,
b
})
</script>

组件基础

传递 props

defineProps();

1
2
3
4
5
6
7
8
<!-- Child.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
<h4>{{ title }}</h4>
</template>
监听事件

defineEmits();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Child.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>

<!-- Parent.vue -->
<Child @enlarge-text="postFontSize += 0.1" title="test"/>

v-modal

在组件上使用v-modal。

方法一:
1
2
3
4
5
6
7
8
9
10
11
12
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
1
<CustomInput v-model="searchText" />
方法二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>

<template>
<input v-model="value" />
</template>

透传 Attributes

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 classstyle 和 id

禁用 Attributes 继承

如果你使用了 <script setup>,你需要一个额外的 <script> 块来书写这个选项声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
// 使用普通的 <script> 来声明选项
export default {
inheritAttrs: false
}
</script>

<script setup>
// ...setup 部分逻辑
</script>
<template>
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>
</template>
多根节点的 Attributes 继承

和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。

1
2
3
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
1
2
3
4
5
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。

 Comments