该篇文章是个人觉得在平常开发过程中没怎么注意到(新增加)的知识点,每个章节的内容在官网中不只文章提到的这些。
💕 模板语法
✔ 动态参数
<script setup>
import { ref } from 'vue'
const attributeName = ref('msg')
const eventName = ref('click')
const handle = () => {
console.log('动态事件触发')
}
</script>
<template>
<!-- 动态事件绑定 -->
<button @[eventName] = "handle">动态事件绑定</button>
<!-- 动态变量绑定 -->
<HelloWorld :[attributeName] = "HelloWorld"/>
</template>
💕 列表渲染
✔ v-for
与对象
<script setup>
import { reactive } from 'vue'
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
</script>
<template>
<ul>
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>
</template>
✔ v-for
使用范围值
注意此处 n
的初值是从 1
开始而非 0
<template>
<ul>
<li v-for="n in 10">{{ n }}</li>
</ul>
</template>
✔ v-for
与 v-if
当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高。
<script setup>
import { reactive } from 'vue'
const todos = reactive([
{ isComplete: true, name: 'work' },
{ isComplete: false, name: 'play' }
])
</script>
<template>
<ul>
<li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}</li>
</ul>
</template>
如果使用上述代码,这会抛出错误和警告
因为:v-if
的优先级高于 v-for
,从而导致 v-for
作用域内定义的变量别名
如何解决:
<script setup>
import { reactive } from 'vue'
const todos = reactive([
{ isComplete: true, name: 'work' },
{ isComplete: false, name: 'play' }
])
</script>
<template>
<ul>
<template v-for="todo in todos">
<li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>
</ul>
</template>
💕 侦听器
✔ 侦听多个数据源
<script setup>
import { watch, ref } from 'vue'
const x = ref(0)
const y = ref(1)
watch([x.value, y.value], ([newx, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
</script>
✔ getter 函数
例子一:
<script setup>
import { watch, reactive } from 'vue'
const obj = reactive({ count: 0 })
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
</script>
上诉代码会报错:因为 watch() 得到的参数是一个 number
例子二:
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
import { watch, reactive } from 'vue'
const obj = reactive({
count: 0
})
watch(obj, (newValue, oldValue) => {
console.log('新值', newValue)
console.log('旧值', oldValue)
})
const handleAcc = () => {
obj.count++
}
</script>
<template>
<button @click="handleAcc">count++</button>
</template>
在嵌套的属性变更时触发,因为它们是同一个对象!故newValue
此处和 oldValue
是相等的
除非 obj
整个被替换掉,才能使 newValue
和 oldValue
不一样
上诉俩个例子都需要将监听函数的第一个参数修改成 getters 函数
watch(() => obj.count, (count) => {
console.log(`count is: ${count}`)
})
✔ watch 第三个参数
watch(() => obj.count, (newCount, oldCount) => {
console.log('立即执行,并深度监听')
}, { deep: true, immediate: true })
注:深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能
上述俩个都比较经常使用的,下面这个配置是控制监听器的触发时机
watch(() => obj.count, (newCount, oldCount) => {
console.log('在这里可以访问 被 vue 更新后的 DOM')
}, { flush: 'post' })
✔ watchEffect
自动跟踪回调的响应式依赖,不需要显性设置监听源。可以自动监听依赖项,并自动触发相关操作
watchEffect(async() => {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
data.value = await response.json()
});
这个例子中,回调会立即执行,不需要指定 immediate: true
。在执行期间,它会自动追踪 todoId.value
作为依赖(和计算属性类似)。每当 todoId.value
变化时,回调会再次执行。有了 watchEffect()
,我们不再需要明确传递 todoId
作为源值。
✔ watchEffect vs watch
watch
和 watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
总结:
- 对于这种只有一个依赖项的例子来说,
watchEffect()
的好处相对较小。 - 对于有多个依赖项的侦听器来说,使用
watchEffect()
可以消除手动维护依赖列表的负担。 - 如果你需要侦听一个嵌套数据结构中的几个属性,
watchEffect()
可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性
✔ 停止监听
在 setup()
或 <script setup>
中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。
如下面这个例子:
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
要手动停止一个侦听器,请调用 watch
或 watchEffect
返回的函数:
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
💕 模板引用
✔ 函数模板引用
下诉代码是模板引用的写法:
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
console.log(inputRef.value)
})
</script>
<template>
<input ref="inputRef" />
</template>
ref
除了可以绑定一个对象外,还能绑定一个函数(函数模板应用):
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
</script>
<template>
<input :ref="(el) => { console.log('input 值', el) /* 将 el 赋值给一个数据属性或 ref 变量 */ }" />
</template>
注:当绑定的元素被卸载时,函数也会被调用一次,此时的 el
参数会是 null
✔ 组件实例引用
也可以通过模板引用来获取到子组件的实例。但这里需要分情况:
- 如果一个子组件使用的是选项式 API 或没有使用
<script setup>
。则父组件对子组件的每一个属性和方法都有完全的访问权。 - 如果使用了
<script setup>
的组件是默认私有的:一个父组件无法访问到一个使用了<script setup>
的子组件中的任何东西。
针对第二种情况:子组件在其中通过 defineExpose
宏显式暴露属性或方法
<script setup>
import { ref, defineExpose } from 'vue'
const a = 1
const b = ref(2)
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>
💕 Props
✔ 如何单向修改 Props
总所周知:Props
是单向数据流,且在子组件不能进行修改。要想修改,只能通知父组件修改,或者使用双向数据绑定。
const props = defineProps(['foo'])
// 修改值
props.foo = 'bar' // 报错
可以通过下述几个方法,在不通知父组件的前提下进行修改:
const props = defineProps(['initialCounter'])
// 将 props 赋值给 counter,则prop 和后续更新无关了
const counter = ref(props.initialCounter)
但有一个缺点:如果上层数据发生改变时,下层是不能实时更新的。可以在做修改:
const props = defineProps(['initialCounter'])
// 将 props 赋值给 counter,则prop 和后续更新无关了
const counter = computed(() => props.initialCounter)
这样就可以保证:
- 如果上层数据发生改变时,下层能够实时更新的
- 下层数据修改时,不会影响到上层数据
💕 事件
✔ 事件校验
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>