vue3官网/基础/笔记

245 阅读4分钟

本篇为个人学习笔记记录

创建一个vue应用

  • 一个应用实例
    • 应用实例:createApp函数创建一个应用(实际是一个组件)
    • 根组件:每一个应用都需要一个根组件
    • 挂载应用:需要调用.mount()方法应用才会被渲染
// 1.createApp函数创建一个新的实例
import { createApp } from 'vue'

// 2.app根组件
const app = createApp({
// 根组件的选项
  data() {
    return {
      count: 0
    }
  }
})

// 3.应用渲染
app.mount('#app')
  • 应用配置
    • 可暴露一个.config对象允许配置应用级选项
    • 提供方法可以注册应用范围内的可用资源,如app.component
// 配置应用级错误处理器
app.config.errorHandler = (err) => {
  /* 处理错误 */
}
// 注册组件
app.component('TodoDeleteButton', TodoDeleteButton)

  • 多个应用创建
    • 允许在同一个页面中创建多个共存的Vue应用,每个应用都有自己的用于配置和全局资源的作用域
const app1 = createApp({
  /* ... */
})
app1.mount('#container-1')

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

模板语法

  • 文本插值,大括号
  • 原始的html,使用v-html指令
  • attribute绑定v-bind
    • 布尔型attribute
    • 动态绑定多个值,绑定一个对象
    • 指令directives
      • v-bind/v-html/v-if/v-for/v-on/v-slot
    • 参数arguments,某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识
  • 同名可简写:id ==> :id=id
  • 仅支持单一的表达式,可使用javaScript表达式(判断方式:是否可以合法的写在return后面)
  • 调用函数,表达式的方法在每次更新都会被重新调用,不会产生任何副作用
  • 受限的全局访问,不能访问用户附加在window上的属性,但可以在app.config.globalProperties上显示添加
  • 动态参数,可以绑定一个计算属性,计算得到的值会被用作一个参数,事件绑定类似,@focus
  • 动态参数值会被限制,必须是一个字符串,可以是null,表示没有,undefind会报错
  • 动态参数语法限制,传入复杂的地台参数,推荐使用计算属性替换复杂的表达式,模版字符串是区分大小写的
  • 修饰符modifiers, - .prevent
// 文本插值
<span>Message: {{ msg }}</span>

// 原始HTML
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

// attribute绑定v-bind
<div v-bind:id="dynamicId"></div>
//简写
<div :id="dynamicId"></div>
// 与 :id="id" 相同 仅支持3.4以上版本
<div :id></div>

//布尔型attribute
<button :disabled="isButtonDisabled">Button</button>
// 动态绑定多个值
const objectOfAttrs = {
  id: 'container',
  class: 'wrapper',
  style: 'background-color:green'
}
<div v-bind="objectOfAttrs"></div>

// 使用javaScript表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>

// 仅支持表达式
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}

// 调用函数
<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>

// 参数Arguments
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>

<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>

// 动态参数
someAttr 复杂的属性使用计算属性表达式 不推荐使用 + -符号
<a :[someAttr]="value"> ... </a>
<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>

//修饰符modifiers
 <form @submit.prevent="onSubmit">...</form>

响应式基础

  • 声明响应式状态
    • ref(),接收参数,对象中有value属性返回,声明一个属性count,ref(0),count.value == 0 ,进行修改
    • 在setup()函数中声明并返回,模版使用ref时,不需要附加.value
    • 组件渲染时,通过追踪ref的变化,触发组件重新渲染,底层用到的是getter和setter方法来拦截对象属性进行get和set操作
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}
// 暴露的方法可以用作事件监听
<button @click="increment">
  {{ count }}
</button>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>
  • 深层响应性
    • 可以改变深层嵌套的对象、数组,内置的数据结果,dom更新不同步,需要用nextTick()全局api
    • 可通过shallow ref 来放弃深层响应性
import { ref } from 'vue'

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

function mutateDeeply() {
  // 以下都会按照期望工作
  // dom 更新不同步
  obj.value.nested.count++
  obj.value.arr.push('baz')
}
    
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
  • reactive()
    • 非原始值,转换为响应式代理,使对象具有响应性,有shallowReactive()退出深层次响应
    • reactive返回的是原始对象的proxy,和原始对象不相等
    • 局限性:有限的值类型,只能用于对象、数组 map set这样的集合类型,不支持string\number\boolean
    • 局限性不能替换整个对象,因为是通过属性访问实现的
    • 对解构操作不友好,会丢失连接
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// 能够拦截对响应式对象所有属性的访问和修改,进行依赖追踪和触发更新
 <button @click="state.count++">
  {{ state.count }}
</button>


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


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

// 返回的是一个原始对象Proxy
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false

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


// 解构操作不友好
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
  • 额外的ref解包细节
    • 嵌套在深层次响应对象属性,才会发生ref的解包
    • 作为响应式对象被访问时会自动解包
    • 一个新的值赋值给一个关联的ref属性,那会替换掉就的ref
// 作为reactive的对象属性,自动解包
const count = ref(0)
const state = reactive({
  count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1

// 已有的ref属性替换掉就的ref
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
  • 数组和集合的注意事项
    • reaxtive()
    • ref()
      • 作为响应式数组或原生集合类型(如Map)被访问时,不会被解包
      • 在模版渲染上下文中,只有顶级的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)

// 模版解包 count object为顶级属性 object.id不是
const count = ref(0)
const object = { id: ref(1) }
{{ count + 1 }}
{{ object.id + 1 }} // 没有解包,错误表达,没有效果
解决方式
const { id } = object
{{ id + 1 }} //渲染结果 2
{{ object.id }} //如果这样 渲染结果 1

计算属性

  • 计算属性基于其响应式依赖被缓存
  • 计算属性,可通过getter和setter可写,setter被调用会更新
  • getter只做计算,不要改变其他状态,也不要做一步请求
  • 避免直接修改计算属性,可用侦听器
<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>

// 获取计算属性的上一个的值
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

const alwaysSmall = computed({
  get(previous) {
    if (count.value <= 3) {
      return count.value
    }

    return previous
  },
  set(newValue) {
    count.value = newValue * 2
  }
})
</script>

类与样式绑定

  • 绑定HTML class
    • 可绑定对象、计算属性、数组、在组件上使用
const classObject = reactive({
  active: true,
  'text-danger': false
})
<div :class="classObject"></div>
// 渲染结果
<div class="active"></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'
}))
// 渲染结果
<div :class="classObject"></div>

// 绑定数组
const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>
// 渲染结果
<div class="active text-danger"></div>

// 在组件上使用
<div :class="classObject"></div>
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />
<p class="foo bar baz boo">Hi!</p>

  • 绑定内敛样式
    • :style支持绑定js对象
// 绑定对象
const styleObject = reactive({
  color: 'red',
  fontSize: '30px'
})
<div :style="styleObject"></div>

// 绑定数组
<div :style="[baseStyles, overridingStyles]"></div>

// 数组仅会渲染浏览器支持的最后一个值
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

条件渲染

  • v-if
    • 值为true时渲染,渲染多次,更高开销
    • 渲染多个时,使用<template />
  • v-show
    • 与v-if不同,初级渲染开销,一直存在,与dispaly:none
  • v-for
    • 不推荐与v-if使用,v-if会先执行,可使用template

列表渲染

  • v-for:遍历数组
    • 可以完整的访问父作用域的属性和变量,index表示索引的位置
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])

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

// 页面结果
Parent-0-Foo
Parent-0-Bar
  • v-for:遍历对象
const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})

<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

// 页面结果
-   0. title: How to do lists in Vue
-   1. author: Jane Doe
-   2. publishedAt: 2016-04-10
  • v-for 使用范围值
    • <templiate />包裹,在v-for与v-if一起使用时
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>
  • 通过key值管理状态
    • 当使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上:
    • key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key。
<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
    • 不会将任何数据传递给组件,组件有独立的作用域
<MyComponent
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
/>
  • 数组变化侦测
    • 能够侦听响应数组的变更方法,js不会更改原数组的,会返回一个新的数组
    • 变更:push(),pop(),shift(),unshift(),splice(),sort(),reverse()
    • return 一个新的数组filter(),concat(),slice()
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>

事件处理

  • 内联事件处理
    • 访问原生dom事件是,可以传入一个$event 变量,或者箭头函数
const count = ref(0)

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



<!-- 使用特殊的 $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)
}
  • 方法事件处理
    • 模版编译器会通过检查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>
  • 事件修饰符
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>


<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 可以改善移动端设备的滚屏性能  -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
  • 按键修饰符
    • 按键别名.enter,.tab,delete,.esc,.space,.up,.down,.left,.right
    • 系统按键修饰符,只有当按键被按下时,才会被触发
      • .ctrl,.alt,.shift,.meta
    • .exact 修饰符
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />


<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>


<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

  • 鼠标按键修饰符
    • .left,.right,.middle限定由鼠标按键触发的事件,基于右手鼠标布局设定,分别指代事件触发器的主、次、辅助,并非实际的物理按键

表单的输入与绑定

v-model可以用于不同类型的表单输入,会根据使用的元素自动对于dom属性和事件

  • 基本用法
<p>Message is: {{ message }}</p>
<!-- 输入框 -->
<input v-model="message" placeholder="edit me" />
<!-- 文本框 不能直接使用大括号 -->
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<!-- 单选框 -->
input type="radio" id="one" value="One" v-model="picked" /> <label for="one">One</label>

<!-- 选择器 -->
const selected = ref('A')
const options = ref([
  { text: 'One', value: 'A' },
  { text: 'Two', value: 'B' },
  { text: 'Three', value: 'C' }
])
<select v-model="selected">
  <option v-for="option in options" :value="option.value">
    {{ option.text }}
  </option>
</select>

<div>Selected: {{ selected }}</div>
  • 值绑定
<!-- `picked` 在被选择时是字符串 "a" -->
<input type="radio" v-model="picked" value="a" />
<!-- `toggle` 只会为 true 或 false -->
<input type="checkbox" v-model="toggle" />
<!-- `selected` 在第一项被选中时为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

<!-- 复选框 -->
<!-- `true-value` 和 `false-value` 是 Vue 特有的 attributes,仅支持和 `v-model` 配套使用。这里 `toggle` 属性的值会在选中时被设为 `'yes'`,取消选择时设为 `'no'`。你同样可以通过 `v-bind` 将其绑定为其他动态值 -->
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

  • 修饰符
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
<!-- 输入为空时,返回空字符串,如果无法被parseFloat处理,将返回原始值 -->
<input v-model.number="age" />
<!-- 去除首尾空格 -->
<input v-model.trim="msg" />

生命周期钩子

  • created初始化编译
  • mounted 可以访问dom节点
  • updated 重新渲染布丁完毕
  • unmounted 取消挂载
graph TD
setup组合式api --> beforeCreate-->created--> beforeMounted --> mounted --> beforeUpdate --> updated --> beforeUnmount --> unmounted

侦听器

  • 组合式apiwatch函数
    • 侦听数据源类型,ref(包括计算属性),一个响应式对象,一个getter函数,不能直接侦听对象的属性
    • {deep: true}深度监听,遍历对象嵌套的属性级数,开销很大
    • {immediate: true},立即监听,数据源发生变化时立即回调
    • {once: true } 一次性侦听器,回调只触发一次
const obj = reactive({ count: 0 })

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

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

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
},
{ deep: true } // 深度监听后,`newValue` 此处和 `oldValue` 是不相等,开销较大,慎用
{ immediate: true } // 即时监听
{ once: true } // 3.4以上版本 回调时至触发一次
)

  • watchEffect
    • 可以直接监听,自动追踪对象的属性做为依赖,当值发生变化时,会立即执行回调函数
    • 相对于watch较小,监听潜逃的数据结构的属性特别适合,只追踪被使用到的属性
const todoId = ref(1)
const data = ref(null)

watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)


// 仅会在同步执行期间,追踪依赖,第一个await正常工作犬访问到的属性才会被追踪
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})
  • watch vs watchEffect

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

    • onWatcherCleanup(),Vue3.5+ 版本支持,注册清理函数,终止过期请求,不能在异步await后调用,调用时间与watchEffect和watch回调函数是同步执行的
    • onCleanup
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
  const controller = new AbortController()
  fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
    // 回调逻辑
  })
  onWatcherCleanup(() => {
    // 终止过期请求
    controller.abort()
  })
})

// onCleanup作为第三个参数传给侦听器回调
watch(id, (newId, oldId, onCleanup) => {
  // ...
  onCleanup(() => {
    // 清理逻辑
  })
})
// onCleanup 作为第一参数
watchEffect((onCleanup) => {
  // ...
  onCleanup(() => {
    // 清理逻辑
  })
})
  • 回调触发的时机
    • 父组件更新后,所有组件的dom更新之前被调用,在侦听器回调小红访问所有组件的dom,则dom将处于更新状态
    • 如果侦听器回调中想要访问vue更新之后的所属组件的dom,需指明flush: 'post'
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

import { watchPostEffect } from 'vue'

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

在vue更新之前触发的侦听器,慎用,可以监听布尔值,避免多次修改数据源

watch(source, callback, {
  flush: 'sync'
})

watchEffect(callback, {
  flush: 'sync'
})

import { watchSyncEffect } from 'vue'

watchSyncEffect(() => {
  /* 在响应式数据变化时同步执行 */
})
  • 停止侦听器 会在宿主组件卸载时,侦听器会自动停止,但需要注意的是,异步回调创建侦听器时,不会绑定到组件上,必须手动停止它,异步创建很少,推荐使用同步创建,用条件判断
import { watchSyncEffect } from 'vue'

watchSyncEffect(() => {
  /* 在响应式数据变化时同步执行 */
})

// 手动停止侦听器
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()

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

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