0、前置条件
1. 预备知识
JavaScript 概述
2. 编辑器设置
VSCode + Volar (and disable Vetur) + TypeScript Vue Plugin (Volar).
一、简介
1. 单文件组件 SFC
单文件组件 ( *.vue 文件,缩写:SFC):将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。
2. API 风格
2.1. 选项式 API (Options API)
核心思想:以“组件实例(this)”的概念为中心,与面向对象语言-基于类的心智模型更为一致。
优点:对初学者更为友好、适用于低复杂度的场景。
用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。
选项所定义的属性都会暴露在函数内部的 this 上,指向当前的组件实例。
2.2.组合式 API (Composition API)
核心思想:直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。需要对Vue 的响应式系统有更深的理解。
优点:形式更自由,便于组织重用。
核心:声明式渲染、响应式状态
使用导入的 API 函数来描述组件逻辑。组合式 API 通常会与 搭配使用。
3. 学习路径
(1)互动教程:边动手边学
(2)阅读指南:深入了解所有细节
(3)查看示例:浏览核心功能、常见界面示例
二、快速上手
1. 创建一个项目
采用构建工具:Vue 官方的构建流程是基于 Vite 的,一个现代、轻量、极速的构建工具。
前提条件:Node版本>15
(1)创建项目
Vue官方的项目脚手架工具,安装并执行create-vue,npm init vue@latest
(2)安装依赖并启动开发服务器:
cd
npm install
npm run dev
(3)发布到生产环境
npm run build
2. 模板语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。
2.1. 文本插值{{}}
最基本的数据绑定形式:文本插值(双大括号)——将会将数据插值为纯文本,而不是 HTML。
Message: {{ msg }}
双大括号标签会被替换为相应组件实例中msg属性的值。
2.2. 原始 HTMLv-html
想插入 HTML,你需要使用 v-html 指令:
2.3. 属性绑定v-bind:属性名
(1)将元素的属性响应式地绑定到状态上,应该使用 v-bind:属性名
指令,简写为:属性名
。
(2)动态绑定多个值:包含多个属性的 JavaScript 对象,通过不带参数的 v-bind,将它们绑定到单个元素上。
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
2.4. 使用 JavaScript 表达式
在 Vue 模板内,支持完整的 JavaScript 表达式:双大括号中、 Vue 指令属性(v-xxx)中
每个绑定仅支持单一表达式:判断方法——可以合法地写在 return 后面。
2.5. 指令 v-xxx
指令由 v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute。
扩展:内置指令
(1)指令条件
通过 v-if 和 v-for 指令条件性地或循环地渲染内容。v-for="item of list1"
指令的任务:在其表达式的值变化时响应式地更新 DOM。
(2)指令参数
某些指令会需要一个“参数”,在指令名v-xxx后通过一个冒号隔开做标识。
v-bind 指令来响应式地更新一个 HTML attribute: ...
v-on 指令将监听 DOM 事件: ...
(3)动态参数
attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。 ...
函数绑定到动态的事件名称上当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus。
(4)修饰符
是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。
.prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault():
3. 响应式
3.0. 响应式状态
中的顶层的导入和变量声明可在同一组件的模板中直接使用。
要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
3.1. reactive()
reactive()函数创建一个响应式对象或数组。
限制:
仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型)。
不可以随意地“替换”一个响应式对象,将导致对初始引用的响应性连接丢失:
限制因为: JavaScript 没有可以作用于所有值类型的 “引用” 机制。
3.2. ref() 存储值
ref() 能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。
应用:经常用于将逻辑提取到组合函数 中。
(1)“ref”:用来存储值的响应式数据源。
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象。
const msg = ref("Hello World!")
(2)通过 .value 属性,访问/修改一个 ref 的值。
msg1.value = msg1.value.split('').reverse().join('')
(3)不需要在模板中写 .value,因为在模板中 ref 会自动“解包”。
(4)通过编译时转换,我们可以让编译器帮我们省去使用 .value 的麻烦let count = $ref(0)
4. 计算属性computed()
4.1. 计算属性API
computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。
computed()创建一个计算属性 ref,动态地根据其他响应式数据源来计算其 .value。
const filtersListData = computed(()=>{
// 返回过滤后的 todo 项目
return showAll.value ? ListData.value : ListData.value.filter((t)=> !t.done)
})
4.2. 计算属性缓存 vs 方法
(1)计算属性值会基于其响应式依赖被缓存。
一个计算属性仅会在其响应式依赖更新时才重新计算。
解释了为什么计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:const now = computed(() => Date.now())
(2)方法调用总是会在重渲染发生时再次执行函数。
为什么需要缓存呢?有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,只用执行一次即可。
4.3. 注意
(1)不要在计算函数中做异步请求或者更改 DOM!
一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。
(2)计算属性的返回值永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。
5. Class 与 Style 绑定 v-bind
Vue 专门为 class 和 style 的 v-bind 用法提供了特殊的功能增强。绑定支持普通字符串、对象、数组。
5.1. class 绑定
(1)对象中写多个字段
:class="{red: isRed}"
:class="{ active: isActive, 'text-danger': hasError }"
(2)直接绑定一个对象
const classObject = reactive({
active: true,
'text-danger': false
})
(3)绑定一个返回对象的计算属性
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
(4)绑定一个数组
const activeClass = ref('active')
const errorClass = ref('text-danger')
(5)有条件地渲染某个class——三元表达式
(6)在数组中嵌套对象:
(7)组件有多个根元素,通过组件的 $attrs 属性来实现指定哪个根元素来接收这个 class
Hi!
This is a child component
Hi!
5.2. style 绑定
:style="{ color: activeColor, fontSize: fontSize + 'px' }"
:style="styleObject"。直接绑定一个样式对象:const styleObject = reactive({color: 'red',fontSize: '13px'})
:style="[baseStyles, overridingStyles]"
6. 列表渲染v-for
6.1. v-for 指令 与 数组
基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式。
支持使用可选的第二个参数表示当前项的位置索引。
也可以使用 of 作为分隔符来替代 in,这更接近 JavaScript 的迭代器语法。
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
{{ item.message }}
{{ item.message }}
{{ item.message }}
6.2. v-for 指令 与 对象
使用 v-for 来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys() 的返回值来决定。
提供第二个参数表示属性名 (key),第三个参数表示位置索引:
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
- {{ value }}
- {{ value }}
- {{ value }}
6.3. v-for与v-if
当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。
这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名,需要在外新包装一层template。
{{ todo.name }}
6.4. 通过 key 管理状态
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。
当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
为了跟踪每个节点的标识,从而重用和重新排序现有的元素,需要提供一个唯一的 key attribute:
6.5. 数组变化侦测
侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。
(1)变更方法
会对调用它们的原数组进行变更。
包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
注意:reverse()和sort()将变更原始数组,计算函数中在调用这些方法之前创建一个原数组的副本:return [...numbers].reverse()
(2)不可变方法
返回一个新数组。
例如:filter()、concat()、slice()
(3)过滤或排序
显示数组经过过滤或排序后的内容,而不实际变更或重置原始数据。
1> 创建返回已过滤或已排序数组的计算属性。
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
{{ n }}
2> 计算属性不可行的情况下(多层嵌套的 v-for 循环)
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
- {{ n }}
7. 事件处理v-on
7.1. 监听事件
使用 v-on 指令 (@) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。v-on:click="methodName" 或 @click="handler"
。
事件处理器的值:内联事件处理器、方法事件处理器。
7.2. 内联事件处理器
通常用于简单场景@click="msg1 += '!'"
7.3. 方法事件处理器
(1)绑定到一个方法/函数,用 @click (v-on:click)语法 。@click="reverseMsg"
(2)访问原生 DOM 事件,传入一个特殊的$event变量/使用内联箭头函数
7.4. 修饰符
(1)事件修饰符
用 . 表示的指令后缀。 包含 .stop .prevent .self .capture .once .passive。
(2)按键修饰符
监听键盘事件。使用 KeyboardEvent.key 暴露的按键名称作为修饰符:@keyup.enter @keyup.page-down
8. 表单输入绑定v-model
v-model 指令在状态和表单输入之间创建双向绑定,表单输入框的内容同步给JS变量。
可以用于各种不同类型的输入:“input、textarea、select” 元素。
注意:v-model 会忽略任何表单元素上初始的 value、checked 或 selected属性,始终将当前绑定的 JavaScript 状态视为数据的正确来源。
8.1. 表单输入框
(1)根据元素自动使用对应的 DOM 属性和事件组合:
- 文本类型的input、textarea元素:绑定value属性并侦听 input 事件;
- radio、checkbox类型的input元素:绑定checked属性并侦听 change 事件;
- select元素:绑定value属性并侦听 change 事件:
const checkedNames = ref([])
{{checkedNames}}
8.2. 值绑定
对于单选按钮、复选框、选择器选项,v-model 绑定的值通常是静态的字符串 (复选框是布尔值):
值绑定为动态数据,使用 v-bind。
9. 侦听器watch
情况:在状态变化时执行一些“副作用”:例如更改 DOM,或根据异步操作结果去修改另一处的状态。
9.1. watch()
懒执行:仅当数据源变化时,才会执行回调。
watch 函数在每次响应式状态发生变化时触发回调函数。
(1)watch() 可以直接侦听一个 ref,并且只要 count 的值改变就会触发回调。
{{countText}}
const count = ref(0)
const countText = ref('')
watch(count,(newV,oldV)=>{
console.log(oldV,newV)
countText.value ='监听数据更新'+ newV
})
(2)watch参数1:不同形式的“数据源”:ref (包括计算属性)、响应式对象、getter 函数、多个数据源组成的数组
// 响应式对象
const obj = reactive({ count: 0 })
watch(obj, ()=>{})
// 提供一个 getter 函数
watch(
() => obj.count, // 不能直接侦听响应式对象的属性值,返回响应式对象的 getter 函数
(count) => {
console.log(`count is: ${count}`)
}
)
// 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}`)
})
9.2. 响应式执行watchEffect
watchEffect:在创建侦听器时,立即执行一遍回调,在相关状态更改时重新请求数据。
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
const API_URL = `https://api.github.com/repos/vuejs/core/commits?per_page=3&sha=`
const branches = ['main', 'v2-compat']
const currentBranch = ref(branches[0])
const commits:Ref = ref(null)
watchEffect(async()=>{
const url = `${API_URL}${currentBranch.value}`
console.log(await fetch(url))
commits.value = await (await fetch(url)).json()
})
- xxx
10. 模板引用ref
Vue 的声明性渲染模型抽象了大部分对 DOM 的直接操作,但我们仍然需要直接访问底层 DOM 元素,可以使用特殊的 ref 属性,获得对元素的直接引用。
10.1. 声明一个ref
声明一个 ref 来存放该元素的引用,必须和模板里的 ref 同名。
(1)当"script setup"执行时,DOM 元素还不存在。模板引用 ref 只能在组件挂载后访问。所以ref使用 null 值来初始化。 ref="pContent"
(2)通过 p.value 访问p标签,对其执行一些 DOM 操作(修改它的 textContent)。
const pContent = ref(null)
onMounted(() => {
pContent.value.textContent='mounted'
})
Hello
10.2. v-for使用ref
ref 中包含的值是一个数组,将在元素被挂载后包含整个列表的所有元素。
const list = ref([/* ... */])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
{{ item }}
10.3. ref绑定函数
:ref
可以绑定为一个函数,会在每次组件更新时都被调用。
该函数会收到元素引用作为其第一个参数:
10.4. 子组件上的 ref
模板引用也可以被用在一个子组件上,父组件通过模板引用获取到了子组件的实例
const child = ref(null)
onMounted(() => {
})
11. 组件基础
命名及引用规则:注册使用驼峰,模板使用短横线。
(1)注册组件:命名-驼峰式、引用--短横线 子组件MyComponent 为注册名; 父组件引用
(2)props参数传递:声明-驼峰式、引用传递-短横线 子组件const props = defineProps(['greetingMessage']); 父组件greeting-message="hello"
(3)emit方法触发自定义事件:声明-驼峰式、监听-短横线 子组件@click="$emit('someEvent'); 父组件@some-event="callback"
11.1. 使用组件
(1)要使用一个子组件,在父组件,通过script setup
,导入的组件都在模板中直接可用。
(2)全局注册一个组件,使得它在当前应用中的任何组件上都可以使用,而不需要额外再导入。
组件注册
每一个组件都维护着自己的状态,是不同的 count。每当你使用一个组件,就创建了一个新的实例。
11.2. 父组件向子组件传参Props
(1)子组件可以通过 props 从父组件接受动态数据。defineProps() 是一个编译时宏,并不需要导入。
注意:需要在子组件的 props 列表上声明注册。
const props = defineProps({
todoItem:Object
})
{{ todoItem!.text }}
11.3. 子组件向父组件触发事件Emits
defineEmits声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证。
子组件emit()的参数1是事件的名称。其他所有参数都将传递给事件监听器。父组件使用@监听子组件触发的事件。
- 声明触发的事件 const emit = defineEmits(['response'])。
- 带参数触发emit('response', 'hello from child')。
const emit = defineEmits(['deleteItem'])
function deleteFun(text:string){
emit('deleteItem',text)
}
11.4. 插槽slot
(1)在子组件中,可以使用 slot 元素作为插槽出口 (slot outlet) 渲染父组件中的插槽内容 (slot content)。
插槽内容可以是任意合法的模板内容:文本、多个元素、组件。
没有提供 name 的slot出口会隐式地命名为“default”。
具名插槽(带 name 的插槽):将多个插槽内容传入到各自目标插槽的出口,使用#name
。
描述信息主体
描述信息-头部
描述信息-尾部
{{ todoItem!.text }}
默认信息========
(2)作用域插槽
渲染作用域:插槽内容本身是在父组件模板中定义的,插槽内容可以访问到父组件的数据作用域,无法访问子组件的数据。
作用域插槽:能够接受参数的插槽,接受的参数只在该插槽作用域内有效。
场景:插槽的内容可能想要同时使用父组件域内和子组件域内的数据,需要让子组件在渲染时将一部分数据提供给插槽。
解决方法:像对组件传递 props 那样,向一个插槽的出口上传递属性。
2.1)默认插槽
子组件传入插槽的 props 作为 v-slot 指令的值,在 v-slot 中可以使用解构。
通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:
{{ slotProps.text }} {{ slotProps.count }}
2.2)具名作用域插槽
插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps"
缩写#name="nameProps"
{{ headerProps }}
(3)作用域插槽场景
逻辑+渲染:封装可重用的逻辑 (数据获取、分页等) 、组合视图界面,只将部分视图输出通过作用域插槽交给了消费者组件来管理。
逻辑(无渲染组件):只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。
说明:大部分无渲染组件实现的功能都可以通过组合式 API 以另一种更高效的方式实现,并且还不会带来额外组件嵌套的开销。
推荐:纯逻辑复用时:使用组合式函数,同时复用逻辑和视图布局时:使用无渲染组件。
5. 动态组件
Tab 界面:
场景:需要在两个组件间来回切换,通过 Vue 的component
元素和特殊的 is 属性实现的。
被传给:is
的值可以是:被注册的组件名、导入的组件对象。
注意:多个组件间作切换时,被切换掉的组件会被卸载。通过
KeepAlive
组件强制被切换掉的组件仍然保持“存活”的状态。