likes
comments
collection
share

vue3 鲜为人知的知识点

作者站长头像
站长
· 阅读数 7

vue3 鲜为人知的知识点

该篇文章是个人觉得在平常开发过程中没怎么注意到(新增加)的知识点,每个章节的内容在官网中不只文章提到的这些。

💕 模板语法

✔ 动态参数

<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>

vue3 鲜为人知的知识点


v-forv-if

当它们同时存在于一个节点上时,v-ifv-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>

如果使用上述代码,这会抛出错误和警告

vue3 鲜为人知的知识点

因为: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>

vue3 鲜为人知的知识点

在嵌套的属性变更时触发,因为它们是同一个对象!故newValue 此处和 oldValue 是相等的

除非 obj 整个被替换掉,才能使 newValueoldValue 不一样

上诉俩个例子都需要将监听函数的第一个参数修改成 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

watchwatchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:

  • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机
  • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确

总结:

  • 对于这种只有一个依赖项的例子来说,watchEffect() 的好处相对较小。
  • 对于有多个依赖项的侦听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。
  • 如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect() 可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性

✔ 停止监听

setup()<script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。

如下面这个例子:

<script setup>
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {})

// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

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

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>