当前位置: 首页 > news >正文

【前端技术】Vue3 技术学习笔记

Vue3 技术学习@前端技术

一、vue3简介

1、选项式API(Options API)

使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑
例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

2、组合式API(Composition API)

通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与

两者关系:

选项式 API 是在组合式 API 的基础上实现的。

选项式的优势:

选项式 API 以“组件实例”的概念为中心 (即上述例子中的 this),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。

组合式的优势:

组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。

二、Vue基础

应用实例

通过createApp()函数进行创建

根组件

传入createApp()的对象实际上是一个组件,每一个应用都需要一个“根组件”,其他组件作为其子组件。
其中:如果使用单文件组件,可以直接从另一个文件中导入根组件。

挂载应用

应用实例必须要在调用 .mound() 方法之后才会被渲染出来。方法接受一个容器参数,可以是一个实际的Dom元素或者是一个css选择器字符串。

.mound()方法应该始终在整体应用配置和资源注册完成后被调用。注意:不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。

Dom中的根组件模板

当在未采用构建流程的情况下使用vue时,我们可以在挂载容器中直接书写根组件模板。
当根组件没有设置template选项时,Vue将自动使用容器的innerHtml作为模板。

应用配置

应用实例会暴露一个.config对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,它将捕获所有由子组件上抛而未被处理的错误

app.config.errorHandler = (err) => {
  /* 处理错误 */
}

应用还提供了一些方法来注册应用范围内可用的资源,例如注册一个组件:

app.component('TodoDeleteButton', TodoDeleteButton)

要确保在挂载应用实例之前完成所有应用配置。

多个应用实例

应用实例并不只限于一个。createApp API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。

const app1 = createApp({
  /* ... */
})
app1.mount('#container-1')

const app2 = createApp({
  /* ... */
})
app2.mount('#container-2')

如果你正在使用 Vue 来增强服务端渲染 HTML,并且只想要 Vue 去控制一个大型页面中特殊的一小部分,应避免将一个单独的 Vue 应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

三、模板语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。

在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。

如果你对虚拟 DOM 的概念比较熟悉,并且偏好直接使用 JavaScript,你也可以结合可选的 JSX 支持直接手写渲染函数而不采用模板。但请注意,这将不会享受到和模板同等级别的编译时优化。

文本插值

双大括号

原始HTML

v-html指令

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Attribute绑定

v-bind指令

<div v-bind:id="dynamicId"></div>

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

简写

<div :id="dynamicId"></div>

布尔型Attribute

布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled 就是最常见的例子之一。

<button :disabled="isButtonDisabled">Button</button>

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

动态绑定多个值

可以通过不带参数的 v-bind,直接将它们绑定到单个元素上:

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

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

使用JavaScript表达式

所有的数据绑定中都支持完整的 JavaScript 表达式,以组件为作用域解析执行。

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

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

仅支持表达式

每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return 后面。

<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}

<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}

调用函数

可以在绑定的表达式中使用一个组件暴露的方法:

<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

受限的全局访问

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math 和 Date。
没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

指令directives

v-bind、v-html、v-on、v-slot、v-for等等。
一个指令的任务是在其表达式的值变化时响应式地更新DOM。

参数Arguments

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。

动态参数

指令参数内可以使用JavaScript表达式, 需要包含在一对方括号内。

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

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

动态参数值的限制

动态参数值表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。

动态参数语法的限制

动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
如果需要传入复杂的动态参数,推荐使用计算属性替换复杂的表达式。

当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写。

例如:

<a :[someAttr]="value"> ... </a>

上面这个例子中,someAttr会被替换为 someattr,这段代码将不会工作。
单文件组件内的模板不受此限制。

修饰符Modifiers

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

<form @submit.prevent="onSubmit">...</form>

四、响应式基础

声明响应式状态

通过reactive()函数创建一个响应式对象或数组:

import { reactive } from 'vue'
const state = reactive({ count: 0 })

响应式对象其实是 JavaScript Proxy,其行为表现与一般对象相似。
不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作。
要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。

import { reactive } from 'vue'

export default {
  // `setup` 是一个专门用于组合式 API 的特殊钩子函数
  setup() {
    const state = reactive({ count: 0 })

	function increment() {
        state.count++
    }

    // 暴露 state 到模板
	// 同时暴露increment函数
    return {
      state,
      increment
    }
  }
}

<script setup>

在 setup() 函数中手动暴露大量的状态和方法非常繁琐。通过使用构建工具来简化该操作。
当使用单文件组件(SFC)时,可以使用

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

const state = reactive({ count: 0 })

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

<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>

<script setup>中的顶层的导入和变量声明可在同一组件的模板中直接使用。
可以理解为模板中的表达式和 <script setup>中的代码处在同一个作用域中。

DOM更新时机 - nextTick函数

当你更改响应式状态后,DOM 也会自动更新。然而,你得注意 DOM 的更新并不是同步的。相反,Vue 将缓冲它们直到更新周期的 “下个时机” 以确保无论你进行了多少次声明更改,每个组件都只需要更新一次。

若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:

import { nextTick } from 'vue'
function increment() {
  state.count++
  nextTick(() => {
    // 访问更新后的 DOM
  })
}

深层响应性

在 Vue 中,状态都是默认深层响应式的。意味着即使在更改深层次的对象或数组,改动也能被检测到。

import { reactive } from 'vue'

const obj = reactive({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // 以下都会按照期望工作
  obj.nested.count++
  obj.arr.push('baz')
}

也可以创建浅层响应式对象,使得仅在顶层具有响应性,一般仅在特殊场景中需要:

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++

响应式代理 vs. 原始对象

值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的:

const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是 仅使用你声明对象的代理版本

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身(该规则对于嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理):

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

// 嵌套对象代理实例:
const proxy = reactive({})

const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false

reactive() 的局限性

reactive() 的2条限制:

  1. 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })

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

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

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() 定义响应式变量

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:

import { ref } from 'vue'
const count = ref(0)

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value。
一个包含对象类型值的 ref 可以响应式地替换整个对象:

const objectRef = ref({ count: 0 })

// 这是响应式的替换
objectRef.value = { count: 1 }

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

const obj = {
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const { foo, bar } = obj

简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。
注:顶层属性: foo 是顶层属性,但 object.foo 不是。

下面是之前的计数器例子,用 ref() 代替:

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

const count = ref(0)

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

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

不是顶层属性渲染的例子:

const object = { foo: ref(1) }
// 下面的表达式将不会像预期的那样工作:
{{ object.foo + 1 }}
// 渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:
const { foo } = object
{{ foo + 1 }}

需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1:

{{ object.foo }}

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1

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

数组和集合类型的 ref 解包

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。

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)

响应性语法糖(实验属性)

相对于普通的 JavaScript 变量,我们不得不用相对繁琐的 .value 来获取 ref 的值。这是一个受限于 JavaScript 语言限制的缺点。然而,通过编译时转换,我们可以让编译器帮我们省去使用 .value 的麻烦。Vue 提供了一种编译时转换,使得我们可以像这样书写之前的“计数器”示例:

<script setup>
let count = $ref(0)

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

<template>
  <button @click="increment">{{ count }}</button>
</template>

五、计算属性

作用:用来描述依赖响应式状态的复杂逻辑。

通过computed()函数方法进行定义,期望接受一个getter函数,返回值为一个计算属性ref。和其他一般的ref类似,可以通过 .value进行访问,计算属性ref也会在模板中自动解包,因此在模板表达式中引用时无需添加.value。

Vue的计算属性会自动追踪响应式依赖。它会检测到依赖关系,当依赖项发生改变时,任何依赖于计算属性的绑定也都会同时更新。

计算属性缓存 vs 方法

计算属性与方法的区别:
相同点:处理结果一样;
不同点:计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。

相比之下,方法调用总是会在重渲染发生时再次执行函数。

Date.now() 并不是一个响应式依赖,所以下面的计算属性永远不会更新:

const now = computed(() => Date.now())

可写计算属性

计算属性默认仅能通过计算函数得出结果,当尝试修改一个计算属性时,会受到运行时警告;
在特殊场景下,如果需要用到“可写“的属性,需要同时提供getter和setter来创建:

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

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

最佳实践

计算函数不应有副作用

计算属性的计算函数应只做计算而没有任何其他的副作用。
不要在计算函数中做异步请求或者更改 DOM!,计算函数职责上应该仅为计算和返回该值。

避免直接修改计算属性值

计算属性返回的是派生状态(临时快照)。当资源状态发生变化时,就会创建新的快照。计算属性的返回值应该被视为只读的。

六、Class 与 Style 绑定

绑定Html class

绑定对象

可以给:class(v-bind:class的缩写)传递一个对象来动态切换class:

// 1、 内联字面量:
// 模板
<div  :class="{ active: isActive, 'text-danger': hasError }"></div>

// 字段操作
const isActive = ref(true)
const hasError = ref(false)

// 渲染结果
<div class="static active"></div>

// 2、 直接绑定对象:
<div :class="classObject"></div>

const classObject = reactive({
  active: true,
  'text-danger': false
})

// 渲染结果同上渲染结果

// 3、 绑定一个返回对象的计算属性
<div :class="classObject"></div>

const isActive = ref(true)
const error = ref(null)

const classObject = computed(() => ({
  active: isActive.value && !error.value,
  'text-danger': error.value && error.value.type === 'fatal'
}))

绑定数组

可以给 :class 绑定一个数组来渲染多个 CSS class:

<div :class="[activeClass, errorClass]"></div>

const activeClass = ref('active')
const errorClass = ref('text-danger')

// 渲染结果
<div class="active text-danger"></div>

// 可以在数组中通过三元表达式来有条件地渲染某个class
<div :class="[isActive ? activeClass : '', errorClass]"></div>

//可以在数组中嵌套对象
<div :class="[{ active: isActive }, errorClass]"></div>

在组件上使用

对于只有一个根元素的组件,当你使用了 class attribute 时,这些 class 会被添加到根元素上,并与该元素上已有的 class 合并,其中: Class绑定也是同样的。

例子: 
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>

<!-- 在使用组件时 -->
<MyComponent class="baz boo" />

// 渲染出的html
<p class="foo bar baz boo">Hi</p>

当组件有多个根元素时,你将需要指定哪个根元素来接收这个class,通过组件的$attrs属性来实现指定:

<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>

<MyComponent class="baz" />

// 渲染结果
<p class="baz">Hi!</p>
<span>This is a child component</span>

绑定内联样式

绑定对象

:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

const activeColor = ref('red')
const fontSize = ref(30)

// 支持 kebab-cased形式的css属性key(对应其css中的实际名称)
<div :style="{ 'font-size': fontSize + 'px' }"></div>

// 直接绑定样式对象,同样的,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。
<div :style="styleObject"></div>
const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})

绑定数组

可以给 :style 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上:

<div :style="[baseStyles, overridingStyles]"></div>

自动前缀

当你在 :style 中使用了需要浏览器特殊前缀的 CSS 属性时,Vue 会自动为他们加上相应的前缀。Vue 是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将测试加上各个浏览器特殊前缀,以找到哪一个是被支持的。

样式多值

你可以对一个样式属性提供多个 (不同前缀的) 值

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex。

七、条件渲染

v-if

条件性渲染

v-else

为v-if添加一个else区块,必须跟在一个v-if 或者 v-else-if 元素后面, 否则它将不会被识别。

v-else-if

v-else-if提供的是对应于v-if的 else if 区块。 它可以连续多次重复使用。

上的 v-if

因为v-if是一个指令,他必须依附于某个元素。
当想要切换不止一个元素时,可以在 元素上使用v-if,最后渲染的结果不会包含这个 元素。

v-show

与v-if的不同之处:

  1. v-show会在dom渲染中保留该元素;v-show仅切换了该元素上名为display的css属性;
  2. v-show不支持在 元素上使用,也不能和v-else搭配使用;

v-if vs v-show

v-if是“真实的“按条件渲染,因为它确保了在切换时,条件区块内的事件监听和子组件都会被销毁与重建;
v-if也是惰性的:如果在初次渲染时条件值为false,则不会做任何事。条件区块只有当条件首次变为true时才被渲染。

对比之下:
v-show无论初始条件如何,始终会被渲染,只有css display属性会被切换;

总的来说:
v-if切换开销更高,而v-show有更高的初始渲染开销。
在频繁切换场景下,使用v-show较好;
在运行时绑定条件很少改变,则v-if会更合适;

v-if 和 v-for

v-if和v-for是不推荐的,因为这样两者的优先级不明显。
当v-if和v-for同时作用在同一个元素上的时候,v-if会首先被执行。

八、列表渲染

v-for

基于一个数组渲染一个列表。v-for指令的值需要使用item in items形式的特殊语法。

v-for 与 对象

可以使用v-for来遍历一个对象的所有属性值。遍历的顺序会基于对该对象调用Object.keys()的返回值来决定。

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})

<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

// 可以通过提供第二个参数表示属性名:
<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

// 第三个参数表示位置索引:
<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

v-for里使用值范围

v-for可以直接接受一个整数值。

<span v-for="n in 10">{{ n }}</span>
// 注意这里n的初值是从1开始而非0

上的 v-for

跟模板上的v-if类似,你也可以在 标签上使用 v-for 来渲染一个包含多个元素的块。例如:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for 与 v-if

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

通过key管理状态

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况。

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

当你使用 时,key 应该被放置在这个 容器上:

<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template>

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

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

组件上使用 v-for

可以直接在组件上使用 v-for,和在一般的元素上使用没有区别 (别忘记提供一个 key):

<MyComponent v-for="item in items" :key="item.id" />

如果需要把数据传递给组件,需要通过props来进行传递:

<MyComponent
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
/>

不自动将 item 注入组件的原因是,这会使组件与 v-for 的工作方式紧密耦合。明确其数据的来源可以使组件在其他情况下重用。

数组变化侦测

变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

替换一个数组

变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:

// `items` 是一个数组的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))

展示过滤或排序后的结果

当希望显示数组经过过滤或排序后的内容,而不实际变更或重置原始数据。在这种情况下,可以创建返回已过滤或已排序数组的计算属性。

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})

<li v-for="n in evenNumbers">{{ n }}</li>

当计算属性不可行的情况下,可以使用以下方法:

const sets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

function even(numbers) {
  return numbers.filter((number) => number % 2 === 0)
}

<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

九、事件处理

监听事件

我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click=“methodName” 或 @click=“handler”。
事件处理器可以选的值:

  1. 内联事件处理器:事件被触发时执行的内联JavaScript语句(与onClick类似)
  2. 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。

内联事件处理器

通常用于简单场景:

const count = ref(0)

<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

方法事件处理器

内联代码方式不够灵活。v-on也可以接受一个方法名或对某个方法的调用。

const name = ref('Vue.js')

function greet(event) {
  alert(`Hello ${name.value}!`)
  // `event` 是 DOM 原生事件
  if (event) {
    alert(event.target.tagName)
  }
}

<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>

方法事件处理器会自动接收原生 DOM 事件并触发执行。在上面的例子中,我们能够通过被触发事件的 event.target.tagName 访问到该 DOM 元素。

方法与内联事件判断

模板编译器会通过检查 v-on 的值是否是合法的 JavaScript 标识符或属性访问路径来断定是何种形式的事件处理器。
后续待更新

相关文章:

  • JS中4种常见的内存泄漏
  • 详解Shiro认证流程
  • 金山办公:订阅为王?
  • 【JavaEE】Spring AOP (面向切面)详解
  • java基于ssm的快递物流管理系统
  • SETTLE约束算法中的坐标变换问题
  • 基于python的家政管理系统毕业设计源码071111
  • SpringBoot基础篇 (3)——基础配置
  • springboot基于web的在线问答社区系统设计与实现毕业设计源码061628
  • 神经网络的基本思路包括,神经网络的基本思路是
  • CREO:CREO软件之工程图【创建】以及配置(符合国家标准)的简介及其使用方法(图文教程)之详细攻略
  • Windows与网络基础-4-安装GNS3软件环境
  • 长安链源码学习v2.2.1--ioc机制(十)
  • 前端工作小结33-确定需求报告
  • SpringMVC 01: SpringMVC + 第一个SpringMVC项目
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • Angular 响应式表单之下拉框
  • CSS 提示工具(Tooltip)
  • ES2017异步函数现已正式可用
  • HTTP中GET与POST的区别 99%的错误认识
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • overflow: hidden IE7无效
  • 理清楚Vue的结构
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 数组大概知多少
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 一起参Ember.js讨论、问答社区。
  • Java性能优化之JVM GC(垃圾回收机制)
  • 带你开发类似Pokemon Go的AR游戏
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #### go map 底层结构 ####
  • (6)设计一个TimeMap
  • (Oracle)SQL优化技巧(一):分页查询
  • (八)Flask之app.route装饰器函数的参数
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (简单) HDU 2612 Find a way,BFS。
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (四) 虚拟摄像头vivi体验
  • (推荐)叮当——中文语音对话机器人
  • (一)80c52学习之旅-起始篇
  • (转)jdk与jre的区别
  • (转)原始图像数据和PDF中的图像数据
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .net 受管制代码
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .Net8 Blazor 尝鲜
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)
  • .NET项目中存在多个web.config文件时的加载顺序
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • [ C++ ] STL_list 使用及其模拟实现
  • [ 数据结构 - C++]红黑树RBTree