Vue中注意点

创建Vue应用的注意点:

1..mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法。-放到最后面去挂载!

2.当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板。

多个应用实例

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

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

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

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


模板语法注意点:

绑定在表达式中的方法在组件每次更新时都会被重新调用,因此应该产生任何副作用,比如改变数据或触发异步操作。

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

受限的全局访问

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 MathDate

没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

动态参数

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

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

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

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

相似地,你还可以将一个函数绑定到动态的事件名称上:

<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething">

动态参数值的限制

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

动态参数语法的限制
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>

还应该避免使用大写,因为浏览器会将它们强制转换成小写的


响应式基础

DOM更新时机nextTick

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

import { nextTick } from 'vue'

export default {
  methods: {
    increment() {
      this.count++
      nextTick(() => {
        // 访问更新后的 DOM
      })
    }
  }
}

响应式对象VS原始对象

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

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

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

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

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理


reactive()的对象的局限性

因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

通过reactive设置的属性可以通过直接通过属性名来使用!

let state = reactive({ count: 0 })

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


用==ref()==定义响应式变量

ref()相对于reactive()可谓是自由了很多!

  • Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref
  • ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:所以在每次使用的时候,我们都要用.value方式!
  • 和响应式对象的属性类似,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 在模板中==作为顶层属性==被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

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

const count = ref(0)

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

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

Tips:

请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, foo 是顶层属性,但 object.foo 不是。

所以我们给出以下 object:

js

const object = { foo: ref(1) }

下面的表达式将不会像预期的那样工作:

template

{{ object.foo + 1 }}

渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

js

const { foo } = object

template

{{ foo + 1 }}

现在渲染结果将是 2

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

template

{{ object.foo }}

这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}

ref在响应式对象中的解包

当一个 ref嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:通过对象来调用这个属性的时候,可以不使用value!

js

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

console.log(state.count) // 0

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

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

js

const otherCount = ref(2)

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

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


数组和集合类型的解包

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

js

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 提供了一种编译时转换,使用$

vue

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

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

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

计算属性

computed返回值为一个计算属性ref。和其它的ref相似,你可以通过.value访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

响应式依赖:

Vue 的计算属性会自动追踪响应式依赖,如果它依赖的那个属性为响应式的数据,那么它本身也变成了一个响应式的数据!


计算属性缓存vs方法:

有时候,我们定义一个方法,然后在模板中使用,获取到的效果与计算属性是一致的。但计算属性它是根据依赖而进行缓存的,一个计算属性仅会在其响应式依赖更新时才重新计算

这也解释了为什么下面的计算属性永远不会更新,因Date.now() 并不是一个响应式依赖:

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

而方法,在每次重新渲染的情况下都会执行,这就造成了性能的损耗!


可写计算属性

计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 gettersetter 来创建:

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

现在当你再运行 fullName.value = 'John Doe' 时,setter 会被调用而 firstNamelastName 会随之更新。


类与样式的绑定

Vue 专门为 classstylev-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组

绑定对象
<div :class="{ active: isActive }"></div>

你可以在对象中写多个字段来操作多个 class。此外,:class 指令也可以和一般的 class attribute 共存。举例来说,下面这样的状态:

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></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'
}))


在组件上使用
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>

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

<p class="foo bar baz boo">Hi</p>

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

$attrs可以访问到父节点上的类名


条件渲染

上的v-if

如果我们想要切换多个元素,可以使用template将他们包裹起来。这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素

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


列表渲染

v-for来遍历对象

第一个参数表示的是键值,第二个参数表示的是键,第三个参数表示的是索引!


v-for里面使用范围值

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

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

注意此处 n 的初值是从 1 开始而非 0


template上面的v-for

template标签里面渲染一个包含多个元素的块


v-forv-if

v-if的优先级要高于v-for,也就是说:当v-if中要用到v-for中的值,v-if是访问不到的!

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

解决方案:

在外面包一层template使用v-for,可以解决这个优先级的问题!


v-for中绑定key值与不绑定key值的区别

假如我们有一个数组 arr = [1,2,3,4],我们要在2后面插入一个值9;

如果绑定了key值,那么会是这样的情况:

只会更新一个dom元素

如果没有绑定key值,那么在此后面的元素都要更新!

更高效地更新虚拟dom

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


数组变化侦测
变更方法#

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

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
替换一个数组#

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

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

你可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,情况并非如此。Vue 实现了一些巧妙的方法来最大化对 DOM 元素的重用,因此用另一个包含部分重叠对象的数组来做替换,仍会是一种非常高效的操作。


事件处理

内联事件处理器中访问事件参数

1.传一个$event变量

2.使用内联箭头函数

<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>

function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault()
  }
  alert(message)
}


事件修饰符

为解决这一问题,Vuev-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
按键修饰符


表单输入绑定

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

原生写法:

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

语法糖:

<input v-model="text">


侦听器

在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数

侦听数据源类型

watch的第一个参数可以是不同形式的数据源,但不能直接侦听响应式对象里面的数据

第一个参数可以是:

  • 一个函数,返回一个值
  • 一个ref
  • 一个响应式对象
  • 或是由以上类型的值组成的数组

第二个参数:侦听源发生变化时要触发的回调函数。

当侦听多个来源时,回调函数接受两个数组,分别对应源数组中的新值和旧值

( [ newValue1, newValue2 ] , [ oldValue1 , oldValue2 ]) => {/* code */

第三个参数:可选对象,可以支持一下这些选项

  • immediate:侦听器创建时立即触发回调
  • deep:如果源是一个对象,会强制深度遍历以便在深层级发生变化时触发回调函数
  • flush:调整回调函数的刷新时机—访问被vue更新后的DOM
  • onTrack / onTrigger:调试侦听器的依赖
const obj = reactive({ count: 0 })

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

解决方法:

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

// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)


深层监听器

直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发

小Tips:

深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能


watchEffect

watch() 是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调

看一个请求数据的小例子:

const url = ref('https://...')
const data = ref(null)

async function fetchData() {
  const response = await fetch(url.value)
  data.value = await response.json()
}

// 立即获取
fetchData()
// ...再侦听 url 变化
watch(url, fetchData)

代码可以简写为:

watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

watchEffect()立即执行一遍回调函数,如果这时函数产生了副作用,Vue 会自动追踪副作用的依赖关系自动分析出响应源


WatchwatchEffect的区别:

这两者主要的区别是追踪响应式依赖的方式:

watch 只追踪明确侦听的数据源

watchEffect 则会在副作用发生期间追踪依赖,他会在同步过程中,追踪到所有能够访问到的响应式属性。依赖关系不那么明确!


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

import { watchPostEffect } from 'vue'

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})


模板引用

Vue声明式渲染为你抽象了对大部分DOM的直接操作,但在某些情况,我们仍然需要直接访问底层DOM元素。要实现这一点,我们有一种ref 属性

<input ref="input">

访问模板引用

为了通过组合式 API 获得该模板引用,我们需要声明一个同名的 ref

<script setup>
import { ref, onMounted } from 'vue'
// 定义的那个变量需要和DOM元素中ref的属性值相等!
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

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

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

注意,你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在呢!

watchEffect(() => {
  if (input.value) {
    input.value.focus()
  } else {
    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
  }
})

v-for 中的模板引用

当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:

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

const list = ref([
  /* ... */
])

const itemRefs = ref([])//注意:这里的变量变成了数组了

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

ref 数组并不保证与源数组相同的顺序


函数模板引用

除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数:

template

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

注意我们这里需要使用动态的 :ref 绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null。你当然也可以绑定一个组件方法而不是内联函数。


组件上的ref

模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例

Tips:遵守标准!

  • 大多数情况下,应该只在绝对需要时才使用组件引用
  • 大多数情况下,你应该首先使用标准的 propsemit 接口来实现父子组件交互。

有一个例外的情况,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

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

const a = 1
const b = ref(2)

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

当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样)。

上面的意思也就是说,如果每个子组件都使用了setup语法,那么如果想通过ref访问里面的内容,则需要手动地去暴露能够访问的变量。利用defineProps去暴露!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值