跳到主要内容

基础

创建Vue工程

可以使用create-vue脚手架快速创建Vue工程项目(typescript,eslint,jsx,prettier,Pinia,vitest等等支持)

npm init vue@latest

✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.

亦可通过vite创建vue项目

yarn create vite

模板语法

文本插值

使用Mustache语法进行文本插值 支持显示原始HTML

<span>Message: {{ msg }}</span>

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
  • 在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞

Attribute 绑定

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

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

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

v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

布尔型Attribute

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

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

绑定多个值

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

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

使用表达式

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

调用函数

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

  • 指令 Directives:指令是带有 v- 前缀的特殊 attribute。
  • 参数 Arguments:某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。
<a v-bind:href="url"> ... </a>

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

<a v-on:click="doSomething"> ... </a>

<!-- 简写 -->
<a @click="doSomething"> ... </a>
  • 动态参数:同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内
  • 数值的限制:动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。
  • 语法的限制:动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

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

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

<!-- 简写 -->
<a @[eventName]="doSomething">
  • 修饰符 Modifiers:修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()
<form @submit.prevent="onSubmit">...</form>

directive

响应式

声明响应式状态

ref()

import { ref } from 'vue'

export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup() {
const count = ref(0)

// 将 ref 暴露给模板
return {
count
}
}
}

/* --------------- */

import { ref } from 'vue'

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

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

// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}

/* --------------- */

<div>{{ count }}</div>

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

reactive()

import { reactive } from 'vue'

const state = reactive({ count: 0 })

<button @click="state.count++">
{{ state.count }}
</button>
缺点
  1. 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。

  2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。

  3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接

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)

计算属性

const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

模板中的表达式虽然方便,但是模板中书写大多逻辑会导致臃肿,难以维护。

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

const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>

计算属性缓存 vs 方法

计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

<p>{{ calculateBooksMessage() }}</p>

// 组件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}

// 下面计算属性永远不会更新,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>
  • Getter 不应有副作用 - 不要在 getter 中做异步请求或者更改 DOM
  • 避免直接修改计算属性值

类与样式绑定

绑定 HTML class

const isActive = ref(true)
const hasError = ref(false)

<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>

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

对象

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

<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'
}))

<div :class="classObject"></div>

// 等同于上例渲染结果
<div class="static active"></div>

数组

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

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

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

// 可使用条件渲染
<div :class="[isActive ? activeClass : '', errorClass]"></div>

// 使用嵌套对象优化
<div :class="[{ active: isActive }, errorClass]"></div>

组件

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

// 渲染结果
<p class="foo bar baz boo">Hi!</p>

<MyComponent :class="{ active: isActive }" />

// 渲染结果
<p class="foo bar active">Hi!</p>


// 手动指定接受class

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

绑定内联样式

绑定对象

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

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

const styleObject = reactive({
color: 'red',
fontSize: '13px'
})

<div :style="styleObject"></div>

绑定数组

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

样式多值

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

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

条件渲染

<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>

template与v-if

<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-show

<h1 v-show="ok">Hello!</h1>
  • v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。

  • v-show 不支持在 template 元素上使用,也不能和 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 会首先被执行。请查看列表渲染指南获取更多细节。

列表渲染

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

<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>

<li v-for="{ message } in items">
{{ message }}
</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>

v-for可遍历对象

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>

使用范围值

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

template上使用v-for

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

key

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

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

过滤,排序

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

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
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>

事件处理

  • 内联事件处理器
const count = ref(0)

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

内联处理器调用方法

function say(message) {
alert(message)
}

<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>
  • 方法事件处理器
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>

按键修饰符

按键别名​

Vue 为一些常用的按键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“Delete”和“Backspace”两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
系统按键修饰符​

你可以使用以下系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
.exact 修饰符

.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。

<!-- 当按下 Ctrl 时,即使同时按下 AltShift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

鼠标按键修饰符

  • .left
  • .right
  • .middle

表单输入绑定

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

{/* v-model 省略上述步骤 */}
<input v-model="text">


<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

{/* 复选框 */}
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

const checkedNames = ref([])

<div>Checked names: {{ checkedNames }}</div>

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>

<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>

<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>

{/* 单选按钮 */}
<div>Picked: {{ picked }}</div>

<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>

<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>

{/* 选择器 */}
<div>Selected: {{ selected }}</div>

<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>

{/* 多选 */}
<div>Selected: {{ selected }}</div>

<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>

值绑定

<!-- `picked` 在被选择时是字符串 "a" -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` 只会为 truefalse -->
<input type="checkbox" v-model="toggle" />

<!-- `selected` 在第一项被选中时为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no" />

<input
type="checkbox"
v-model="toggle"
:true-value="dynamicTrueValue"
:false-value="dynamicFalseValue" />

<input
type="checkbox"
v-model="toggle"
:true-value="dynamicTrueValue"
:false-value="dynamicFalseValue" />

<select v-model="selected">
<!-- 内联对象字面量 -->
<option :value="{ number: 123 }">123</option>
</select>

修饰符

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

// 用户输入自动转换为数字
<input v-model.number="age" />

// 默认自动去除用户输入内容中两端的空格
<input v-model.trim="msg" />

生命周期

lifecycle

侦听器

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

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>

<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})

// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})

/* ------ */
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}`)
}
)

深层侦听器


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

watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})

obj.count++

// 相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:
watch(
() => state.someObject,
() => {
// 仅当 state.someObject 被替换时触发
}
)

// 你也可以给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)

即时回调的侦听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。

我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行

watch(source, (newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
}, { immediate: true })

watchEffect()

每当 todoId 的引用发生变化时使用侦听器来加载一个远程资源

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

// 使用watchEffect简化
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})

// 它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。

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

watch vs. watchEffect​

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

  • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。

  • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确

停止侦听器

在 setup() 或 script setup 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。

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

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

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

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

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

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

// 注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:
// 需要异步请求得到的数据
const data = ref(null)

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

模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。

访问模板引用

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

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

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

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

/* -------- */
export default {
setup() {
const input = ref(null)
// ...
return {
input
}
}
}


// 只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。
watchEffect(() => {
if (input.value) {
input.value.focus()
} else {
// 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
}
})

函数模板引用

除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。

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

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

组件上的 ref

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

const child = ref(null)

onMounted(() => {
// child.value 是 <Child /> 组件的实例
})
</script>

<template>
<Child ref="child" />
</template>


// 使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>

组件基础

自定义组件

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

const count = ref(0)
</script>

<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>

// 使用组件
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>

传递 props

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
<h4>{{ title }}</h4>
</template>

// ---

const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])

<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>

监听事件

<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import BlogPost from './BlogPost.vue'

const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])

const postFontSize = ref(1)
</script>

<template>
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
@enlarge-text="postFontSize += 0.1"
></BlogPost>
</div>
</template>

插槽分配内容

slot 元素来实现

<AlertBox>
Something bad happened.
</AlertBox>

<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>

<style scoped>
.alert-box {
/* ... */
}
</style>

使用 slot 作为一个占位符,父组件传递进来的内容就会渲染在这里。

动态组件

import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'

const currentTab = ref('Home')

const tabs = {
Home,
Posts,
Archive
}
</script>

<template>
<div class="demo">
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="currentTab = tab"
>
{{ tab }}
</button>
<component :is="tabs[currentTab]" class="tab"></component>
</div>
</template>

大小写区分

HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:

// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}

<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

闭合标签

在 DOM 模板中,我们必须显式地写出关闭标签:

<my-component /> <!-- 我们想要在这里关闭标签... -->
<span>hello</span>

将被解析为:

<my-component>
<span>hello</span>
</my-component> <!-- 但浏览器会在这里关闭标签 -->

元素位置限制

<ul\><ol\><table\><select\>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li\><tr\><option\>
<table>
<blog-post-row></blog-post-row>
</table>

自定义的组件 blog-post-row 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

<table>
<tr is="vue:blog-post-row"></tr>
</table>