重学Vue3-基础

232 阅读9分钟

v-bind

想要响应式地绑定一个 attribute,应该使用 v-bind 指令

如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除

然而,对于一个自定义组件。这不意味着拿不到props,只不过他的值是undefined或null

同名简写

只在 Vue 3.4 及以上版本中可用

<!-- 与 :id="id" 相同 -->
<div :id></div>

<!-- 这也同样有效 -->
<div v-bind:id></div>

布尔型 Attribute

当 isButtonDisabled 为真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。

动态绑定多个值

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

<div v-bind="objectOfAttrs"></div>

使用 JavaScript 表达式

在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:

  • 在文本插值中 (双大括号)
  • 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中

v-model

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦:

<input
  :value="text"
  @input="event => text = event.target.value">

v-model 指令帮我们简化了这一步骤:

<input v-model="text">

另外,v-model 还可以用于各种不同类型的输入,<textarea><select> 元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合

修饰符

.lazy

默认情况下,v-model 会在每次 input 事件后更新数据。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:

<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />

.number

如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:

<input v-model.number="age" />

如果该值无法被 parseFloat() 处理,那么将返回原始值。

number 修饰符会在输入框有 type="number" 时自动启用。

.trim

如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符

v-for

v-for遍历数组

基本使用不再赘述

v-for里可以解构

<li v-for="({ message }, index) in items"> 
    {{ message }} {{ index }} 
</li>

你也可以使用 of 作为分隔符来替代 in

<div v-for="item of items"></div>

v-for也可以遍历对象

遍历的顺序会基于对该对象调用 Object.keys() 的返回值来决定
不同的是第二个参数表示属性名,第三个参数表示位置索引

在 v-for 里使用范围值

v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n 的取值范围重复多次。初值是从1开始。

<span v-for="n in 10">{{ n }}</span>

v-forv-if

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!-- 这会抛出一个错误,因为属性 todo 此时 没有在该实例上定义 -->
<li v-for="todo in todos" v-if="!todo.isComplete"> 
    {{ todo.name }} 
</li>

Vue官网给出了两种解决方案

  1. 使用计算属性先过滤原数组,再迭代这个计算属性
  2. 在外先包装一层 <template> 再在其上使用 v-for 可以解决这个问题,官网更推荐这种,简单明了。
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

通过 key 管理状态

没有key的情况下,更新策略是

公共长度部分节点打补丁

根据长度移除多余的节点或者挂载新节点

所以不会调整元素的顺序,在公共部分会依次比较元素的类型,如果类型一致会重用,不会销毁。

这样重用可能会有问题

<script setup>
import { reactive, ref } from 'vue'

const arr=reactive([1,2,3,4,5,6])
function add(){
  arr.splice(2,1)
}
</script>

<template>
  <div v-for="item in arr" >
  <input  />
  </div>
  <div @click="add">点击</div>
</template>

image.png

但是点击之后,不是我们想删的元素

image.png

如果带上了key,会使用diff算法

将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。

同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。

推荐在任何可行的时候为 v-for 提供一个 key attribute,除非所迭代的 DOM 内容非常简单 (例如:不包含组件或有状态的 DOM 元素),或者你想有意采用默认行为来提高性能。

key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key。

指令 Directives

参数 Arguments

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute:

<a v-bind:href="url"> ... </a>

<!-- 简写 -->
<a :href="url"> ... </a>

动态参数

<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

修饰符 Modifiers

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

directive.DtZKvoAo.png

ref 解包细节

主要是ref作为对象/数组内的元素,以及在模板上的解包不同

作为响应式对象的属性

一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说,它的行为就像一个普通的属性:

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

const msg = ref('Hello World!')
const data= ref({
  msg
})
console.log(data.value.msg)//相当于普通属性访问
</script>

<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg" />
</template>

<script setup>
import { reactive, ref } from 'vue'

const msg = ref('Hello World!')
const data= reactive({
  msg
})
console.log(data.msg)//相当于普通属性访问
</script>

<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg" />
</template>

只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。

数组和集合的注意事项

ref 作为响应式数组或原生集合类型 (如 Map) 中的元素被访问时,它不会被解包:

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

const books = (ref/reactive)([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books.value[0])
</script>

在模板中解包的注意事项

在模板渲染上下文中,只有顶级的 ref 属性才会被解包。

在下面的例子中,count 和 object 是顶级属性,但 object.id 不是:

const count = ref(0)
const object = { id: ref(1) }

{{ count + 1 }}//可以拿到
{{ object.id + 1 }}//拿不到值,因为不是顶级属性

另一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 {{ }} 标签),那么它将被解包,因此以下内容将渲染为 1

{{ object.id }} //等价于{{ object.id.value }}
{{ object.id + 1 }}//不能

模板引用

ref 在v-for上的使用

此时它将是一个数组

ref在自定义组件上的使用

通过defineExpose使用该组件暴露出的数据和方法

生命周期钩子

onMounted

其所有同步子组件都已经被挂载 (不包含异步组件或 <Suspense> 树内的组件)。

onUpdated

注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。

父组件的更新钩子将在其子组件的更新钩子之后调用。

这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行 (考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。

此时可以拿到最新的dom

onUnmounted

需要所有子组件先被卸载

onBeforeMount

已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

侦听器

侦听数据源类型

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

深层侦听器

watch传入一个reactive类型的数据,会自动创建深层监听器。相当于deep:true.监听其他类型都不会深层监听

watchEffect

如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect() 可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。

watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。也就是await之后的响应式数据不会被跟踪

回调的触发时机

修改了响应式数据,可能会同时触发 Vue 组件更新和侦听器回调

类似于组件更新,用户创建的侦听器回调函数也会被批处理以避免重复调用

默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。

Post Watchers

如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: 'post' 选项

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()

同步侦听器

监听的响应式数据修改后立即执行回调,更加拿不到最新的dom

同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发(开销大)。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。

举个例子,在定时器中对监听的响应式数据进行5次修改

   setup() {
          const data =reactive({a:1,b:2})
          watch(()=>data.a+data.b,()=>{
          console.log(data,'watch')
          },{
            flush:'sync'
          })
          setTimeout(()=>{
            data.a+=1
            data.a+=1
            data.a+=1
            data.a+=1
            data.b-=1
          },2000)
          const message = ref('Hello vue!')
          return {
            message
          }
        }

打印结果,触发五次cb

image.png

使用默认属性,之后打印一次,因为批处理

异步创建监听器

如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏

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

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

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

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

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

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

// 需要异步请求得到的数据
const data = ref(null)

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

特殊情况

      setup() {
          const data =reactive({a:1,b:2})
          watch(()=>data.a+data.b,()=>{
          console.log(data,'watch')
          })
          setTimeout(()=>{
            data.a+=1
            data.b-=1
          },2000)
          const message = ref('Hello vue!')
          return {
            message
          }
        }

这样不会触发cb,因为是比较getter函数返回值是否改变才执行cb

image.png

可以看到如果设置了deep为true或者是浅响应会触发cb的