Contents

Vue-2基础

Contents

本系列是作者在跟着Vue官网学习时做的笔记,可能并不详尽,读者可以到官网中查看完整内容。本文只讲解带有<script setup>组合式API。

创建一个 Vue 应用

应用实例(APP)

组件其实就是一个对象(后面会讲),可以从vue文件导出(这样的组件是可复用的),具有一些特定的属性和方法。

通过一个组件来创建app

1
2
3
4
5
import { createApp } from 'vue'

const app = createApp({
  /* 根组件选项 */
})

根组件(vue)

创建app的组件叫根组件

1
2
3
import App from './App.vue'

const app = createApp(App)

挂载应用

app.mount(“xxx”)将app挂载到index.html来渲染页面

DOM 中的根组件模板

index.html

1
2
3
<div id="app">
  <button @click="count++">{{ count }}</button>
</div>

main.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { createApp } from 'vue'

const app = createApp({
  data() {
    return {
      count: 0
    }
  }
})

app.mount('#app')

当app的根组件没有 template 选项(组合式API也使用术语选项,但是其实表现为属性)时,Vue 将自动使用#app容器的 innerHTML 作为模板

应用配置

app.config对象暴露对应用配置的API,详见官网

app.component(“注册名称”.组件)方法注册应用范围组件:app.component('TodoDeleteButton', TodoDeleteButton)

多个应用实例

一个页面可以绑定多个app

1
2
3
4
5
6
7
8
9
const app1 = createApp({
  /* ... */
})
app1.mount('#container-1')

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

模板语法

文本插值

template是vue组件的一个选项(属性),和html写法一致

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

{{ msg }}就是文本插值,会被替换为该组件实例的msg属性值,这种属性一般都是响应式的,js中修改这些属性值会触发页面重新渲染以同步修改

原始 HTML

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

指令(其实应该是template元素的属性)由 v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute

v-html指令在当前组件实例上,将此元素的 innerHTML 与 rawHtml 属性绑定

不推荐使用v-html,容易造成XSS漏洞

Attribute 绑定

v-bind 指令:template元素属性值和组件属性值单向绑定(改变组件属性值会触发template元素属性值修改)

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

简写:

:

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

布尔型 Attribute:

  • 组件属性为真——template元素具有该属性
  • 组件属性为假——template元素没有该属性

动态绑定多个值:

即通过"v-bind"(注意不是"v-bind:xxx")属性给template(后面统称页面)元素绑定组件的一个对象属性,对象属性的每一对键值为页面元素的属性名和绑定值

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

使用 JavaScript 表达式

js表达式可以应用于:

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

仅支持单一表达式

可以在表达式中调用函数

仅能够访问到全局对象列表中的全局对象, app.config.globalProperties 可以添加对象到该列表以供其他组件使用

指令 Directives

v-开头的特殊template元素属性。

参数 Arguments:指令:后面的标识符

动态参数:[]括起来的标识符,应该为组件的一个数据属性

动态参数值应当是一个字符串,或者是 null(显式移除该绑定)

不要使用复杂的动态参数,用计算数学代替

修饰符 Modifiers 以点开头的指令后缀,表示特殊绑定,在执行绑定渲染之前先执行一些其他的任务。

https://cn.vuejs.org/assets/directive.69c37117.png

响应式基础

声明响应式状态

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

1
2
3
import { reactive } from 'vue'

const state = reactive({ count: 0 })

响应式对象其实是 JavaScript Proxy实现的

在 setup() 函数中定义并返回响应式对象或者更新响应式状态的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({ count: 0 })

    function increment() {
      state.count++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      state,
      increment
    }
  }
}
1
2
3
<button @click="increment">
  {{ state.count }}
</button>

<script setup>可以省略setup()函数暴露的过程,即js里面定义的响应式对象直接作为组件的属性。

DOM 更新时机:响应式状态改变和DOM(渲染)更新不是同步的,会等到更新周期的“下个时机”更新DOM,在这期间的修改只取最后一次的结果

nextTick()全局API可以让函数在更新完之后执行

1
2
3
4
5
6
7
8
import { nextTick } from 'vue'

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

深层响应性:响应式状态内部的对象或数组改变也会更新。可以定义一个浅层响应式对象

响应式代理 vs. 原始对象:

  • reactive() 返回的是一个原始对象的 Proxy,并不相等。
  • 修改原始对象不能触发更新,仅使用reactive()的返回值
  • 对代理对象调用reactive()会返回它自己
  • 响应式对象内的对象仍然是代理
1
2
3
4
5
6
const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false

reactive() 的局限性

  • 仅对对象类型有效,对 string、number 和 boolean 这样的 原始类型 无效
  • 通过属性访问进行追踪,必须始终保持对该响应式对象的相同引用。随意地“替换”一个响应式对象将导致对初始引用的响应性连接丢失
1
2
3
4
let state = reactive({ count: 0 })

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

用 ref() 定义响应式变量

ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:

1
2
3
import { ref } from 'vue'

const count = ref(0)

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

1
2
3
4
5
6
7
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 可以响应式地替换整个对象,也可以赋值给本地变量

ref 在模板中的解包:详见官网,我个人认为解包这个语法糖会增加vue的学习难度,对于Ref尽量都不要自动解包

计算属性

computed()方法接收一个箭头函数,返回值为一个计算属性 ref。计算属性可以用.value访问。计算属性和函数体中使用的属性会建立依赖,他们会同步更新

计算属性缓存 vs 方法

相比于在表达式中调用函数,计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。方法调用总是会在重渲染发生时再次执行函数。

可写计算属性

计算属性默认是只读的即只传递一个箭头函数。当传递一个具有getter 和 setter 的对象时为可写计算属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<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 会被调用而 firstName 和 lastName 会随之更新

最佳实践

不要在 getter 中做异步请求或者更改 DOM

避免直接修改计算属性值

尽量不要用可写计算属性

Class 与 Style 绑定

绑定 HTML class

绑定对象:

1
<div :class="{ active: isActive }"></div>

值为bool的class对象属性和template属性绑定类似,即真为有该属性,假为没有该属性

:class 指令可以和一般的 class attribute 共存

:class 可以绑定一个返回对象的计算属性

绑定数组

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

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

可以使用三元表达式:

1
<div :class="[isActive ? activeClass : '', errorClass]"></div>

可以在数组中嵌套对象

1
<div :class="[{ active: isActive }, errorClass]"></div>

在组件上使用

只有一个根元素的组件,根元素会继承组件上的class属性,并与该元素上已有的 class 合并

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

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

绑定内联样式(style)

绑定对象

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

支持 kebab-cased 和camelCase 形式的 CSS 属性 key,推荐使用 camelCase(html不区分大小写,编译器帮忙转换)

可以使用返回样式对象的计算属性

绑定数组

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

自动前缀

Vue 会为需要浏览器特殊前缀的CSS属性自动加上相应的浏览器前缀

样式多值

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

数组仅会渲染浏览器支持的最后一个值。

条件渲染

v-if

1
<h1 v-if="awesome">Vue is awesome!</h1>

v-else

1
2
3
4
<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

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

v-else-if

和 v-else 类似,必须紧跟在一个 v-if 或一个 v-else-if 元素后面。

可以将多个元素放到<template> 元素中使用该元素上的条件渲染属性统一进行条件渲染,<template>只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。

v-show

1
<h1 v-show="ok">Hello!</h1>

具有v-show为false的元素会保留在DOM中,v-if为false的元素不会出现在DOM中,v-show仅切换了display style属性

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 会首先被执行。

列表渲染

v-for

v-for 指令的值需要使用 item in items 形式的特殊语法

1
2
3
<li v-for="item in items">
  {{ item.message }}
</li>

v-for 块中可以完整地访问父作用域内的属性和变量

v-for 也支持使用可选的第二个参数表示当前项的位置索引

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

可以在定义 v-for 的变量别名时使用解构,和解构函数参数类似:

1
2
3
4
5
6
7
8
<li v-for="{ message } in items">
  {{ message }}
</li>

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

多层嵌套的 v-for,每个 v-for 作用域都可以访问到父级作用域:

1
2
3
4
5
<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>

可以用of代替in(不推荐,甚至在js里也不推荐,会增加语言学习难度)

v-for 与对象

可以使用 v-for 来遍历一个对象的所有属性(使用Object.keys()实现)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<li v-for="value in myObject">
  {{ value }}
</li>

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

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

在 v-for 里使用范围值

类似range(n),n从1开始迭代

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

上的 v-for

1
2
3
4
5
6
<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

通过 key 管理状态

key 让 Vue 跟踪每个节点,从而重用和重新排序现有的元素

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

key 绑定基础类型。不要用对象。

组件上使用 v-for

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

数组变化侦测

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

使用这些方法更新响应式数组

展示过滤或排序后的结果

将数组排序后再给v-for,可以使用返回数组的计算属性或者函数

注意,reverse() 和 sort()会修改原数组

1
2
- return numbers.reverse()
+ return [...numbers].reverse()

事件处理

监听事件

v-on 指令 (简写为 @)

  1. 内联事件处理器:内联 JavaScript 语句
  2. 方法事件处理器:组件上定义的方法的属性名或是路径

内联事件处理器

1
const count = ref(0)
1
2
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

方法事件处理器

1
2
3
4
5
6
7
function greet(event) {
  alert(`Hello ${name.value}!`)
  // `event` 是 DOM 原生事件
  if (event) {
    alert(event.target.tagName)
  }
}
1
2
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>

方法事件处理器会自动接收原生 DOM 事件并触发执行

在内联处理器中调用方法

可以在内联事件处理器中调用方法,向方法传入自定义参数以代替原生事件

1
2
3
function say(message) {
  alert(message)
}
1
2
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

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

使用$event 变量在内联事件处理器中访问原生 DOM 事件, 或者使用箭头函数

1
2
3
4
5
6
7
8
9
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>
1
2
3
4
5
6
7
function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault()
  }
  alert(message)
}

事件修饰符

  • .stop:停止传递
  • .prevent:不重新加载页面
  • .self:忽略来自子元素的事件
  • .capture:使用 capture 捕获模式
  • .once:最多被触发一次
  • .passive:事件默认行为立即发生,不等待绑定函数执行完成

可以链式调用,先调用的对后面的都有效

按键修饰符

用于键盘事件,KeyboardEvent.key,key使用kebab-case

1
2
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />

按键别名:

.enter .tab .delete (捕获“Delete”和“Backspace”两个按键) .esc .space .up .down .left .right

系统按键修饰符:

.ctrl .alt .shift .meta

1
2
3
4
5
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

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

.exact 修饰符:仅允许确定组合的事件

1
2
3
4
5
6
7
8
<!-- 当按下 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

表单输入绑定

1
<input v-model="text">

基本用法

文本:绑定字符串变量

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

多行文本:绑定字符串变量

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

<textarea> 中是不支持插值表达式

1
2
3
4
5
<!-- 错误 -->
<textarea>{{ text }}</textarea>

<!-- 正确 -->
<textarea v-model="text"></textarea>

复选框:绑定bool或者数组

1
2
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

将多个复选框绑定到同一个数组或集合的值:

1
const checkedNames = ref([])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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>

单选按钮:绑定字符串变量

1
2
3
4
5
6
7
<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>

选择器:绑定字符串变量或数组

单选

1
2
3
4
5
6
7
8
<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>

多选

1
2
3
4
5
6
7
<div>Selected: {{ selected }}</div>

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

option 可以用 v-for 动态渲染

值绑定

v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值)

v-bind 可以绑定为非字符串的数据类型,可以将之前绑定静态字符串的value等属性用v-bind绑定

复选框

1
2
3
4
5
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用

可以通过 v-bind 将其绑定为其他动态值

1
2
3
4
5
<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

单选按钮

1
2
<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />

选择器选项

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

修饰符

.lazy:默认是input 事件后更新数据,该修饰符改为在每次 change 事件后更新数据

.number:输入自动转换为数字,无法被 parseFloat() 处理时将返回原始值。number 修饰符会在输入框有 type=“number” 时自动启用

.trim:自动去除用户输入内容中两端的空格

生命周期钩子

https://cn.vuejs.org/assets/lifecycle.16e4c08e.png

直接使用全局API,通常是on+生命周期钩子名称(即图中的红色框),使用camelCase

1
2
3
4
5
6
7
<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

侦听器(watch)

基本示例

侦听数据源类型

全局API watch(数据源,回调函数)

数据源是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:不能直接侦听响应式对象的属性值,可以用一个返回该属性的 getter 函数代替

回调函数可以接收old值和new值。且通常是箭头函数。

1
2
3
4
5
6
7
// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

深层侦听器

watch侦听响应式对象默认是深层侦听的,响应式对象属性发生改变也会触发回调

侦听getter函数时返回不同的对象时才触发回调,可以显式使用deep选项转换为深层侦听

1
2
3
4
5
6
7
8
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

watchEffect(回调函数)

立即执行回调函数,并追踪函数体中的依赖关系分析出响应源。

watch vs. watchEffect:主要区别是追踪响应式依赖的方式

  • watch 只追踪明确侦听的数据源。懒加载,定义时不会执行。
  • watchEffect 在副作用发生期间追踪依赖。定义时会执行。

回调的触发时机

Vue 组件更新之前。回调中访问的 DOM 将是被 Vue 更新之前的状态。

flush: ‘post’ 选项指明组件更新后执行

1
2
3
4
5
6
7
watch(source, callback, {
  flush: 'post'
})

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

watchPostEffect()集成了该选项

1
2
3
4
5
import { watchPostEffect } from 'vue'

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

停止侦听器

用同步语句创建的侦听器会在宿主组件卸载时自动停止

异步回调创建的侦听器不会绑定到当前组件上,必须手动停止它,以防内存泄漏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script setup>
import { watchEffect } from 'vue'

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

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

调用 watch 或 watchEffect 返回的函数停止一个侦听器:

1
2
3
4
const unwatch = watchEffect(() => {})

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

模板引用

通过 ref 属性获取底层 DOM 元素

1
<input ref="input">

访问模板引用

声明一个同名的 ref

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script setup>
import { ref, onMounted } from 'vue'

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

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

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

在组件挂载后才能访问模板引用,否则为null,编程时需要考虑 null 的情况:

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

v-for 中的模板引用

v-for元素ref attribute 使用数组ref绑定,ref数组不保证和html相同

函数模板引用

ref attribute 绑定(使用v-bind或者:)为一个函数,组件更新时被调用,第一个参数为DOM元素引用

组件上的 ref

引用中获得的值是组件实例

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

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

尽量避免使用组件ref,减少组件间的耦合

组件基础

https://cn.vuejs.org/assets/components.7fbb3771.png

定义一个组件

.vue 文件:单文件组件 (简称 SFC)

1
2
3
4
5
6
7
8
9
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

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

使用组件

<script setup>组件将会以默认导出的形式被暴露给外部,导入的组件都在模板中直接可用。

全局地注册一个组件在当前应用中的任何组件上都可以使用

组件可以被重用任意多次,每一个组件都维护着自己的状态

传递 props

defineProps 宏:声明的 props 会自动暴露给模板

1
2
3
4
5
6
7
8
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

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

返回一个对象,其中包含了可以传递给组件的所有 props

1
2
const props = defineProps(['title'])
console.log(props.title)

一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值

父组件传递数据给子组件:

1
2
3
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

v-bind 可以传递动态 prop 值

监听事件

父组件可以通过 v-on 或 @ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样。

1
2
3
4
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件

1
2
3
4
5
6
7
<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

通过 defineEmits 宏来声明需要抛出的事件

1
2
3
4
5
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

通过插槽来分配内容

子组件中传递HTML内容

父组件:

1
2
3
<AlertBox>
  Something bad happened.
</AlertBox>

子组件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

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

<slot> 元素表示父组件html渲染位置

动态组件

通过组件的is属性实现

1
2
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

值可以是:

  • 被注册的组件名
  • 导入的组件对象

<KeepAlive> 组件强制被切换掉的组件仍然保持“存活”的状态

1
2
3
4
<!-- 非活跃的组件将会被缓存 -->
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>

DOM 模板解析注意事项

见官网,没啥必要学,反正都是直接用单文件组件开发

 |