Vue
构建用户界面、渐进式(工程规模由小变大)。
- 组件化(Components) 提高代码复用率。(复用、封装)
- 声明式编码 (Declarative Rendering) 代码更加直观。 (数据驱动视图)
- 虚拟 DOM (Virtual DOM) + Diff 算法 提高渲染性能。
- 响应式数据流 (Reactive Data Binding) 数据变化自动更新视图。
- 生命周期钩子 (Lifecycle Hooks) 在特定阶段执行特定逻辑。
- 插槽 (Slots) 提供灵活的组件组合方式。
- 计算属性 (Computed Properties) 缓存计算结果,提高性能。
- 侦听器 (Watchers) 监听数据变化,执行异步操作。
- 路由 (Router) 导航和视图映射。
学习目标
- 先学会用数据在 template 和 style 里面学写 HTML 和 CSS 样式,在 script 里学写响应式数据,数模分离实现在 view 里面渲染
- 学路由规则的设计和跳转的逻辑
- 实现一个独立的单组件,学会跨组件、跨视图的数据通信( props + pinia ),还有表单里面的 v-model
- 学下 axios 组件 ,学会在 script 里面调用接口获得动态数据
- 理解下 jwt 的工作原理,学下通过 axios 如何添加请求拦截器,如何携带 token
- Vue 基础
- Vue CLI
- Vue-Router
- Vuex
- Element-UI
- Vue3
API 风格
选项式 API
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。 选项所定义的属性都会暴露在函数内部的
this
上,它会指向当前的组件实例。
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式 API
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与
<script setup>
搭配使用。这个 setup
attribute 是一个标识,告诉 Vue
需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
选项式 API 以“组件实例”的概念为中心 (即上述例子中的 this
)
,对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。
组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。
基础
模板语法
<template>
<!-- v-bind:disabled ==> :disabled v-on:click ==> @click -->
<button :disabled="isButtonDisabled" @click="count++" @[eventName]="count+=2">count: {{ count }}</button>
<p>{{ msg }}</p>
<p>{{ msg.split('').reverse().join('') }}</p>
<p>{{ count == 1 ? msg : 'error' }}</p>
<p>{{ rawHtml }}</p>
<p v-bind:id="dynamicId" v-html="rawHtml"></p>
<p :id="dynamicId" v-html="rawHtml"></p>
<div v-bind="objectOfAttrs"></div>
<p v-if="seen">Now you see me</p>
<a :[attrName]="attrValue" :href="url">{{attrValue}}</a>
</template>
<script>
export default {
data() {
return {
count: 0,
msg: 'This is a message!',
rawHtml: '<span style="color: red">This should be red.</span>',
dynamicId: 'dynamicId',
isButtonDisabled: false,
objectOfAttrs: {
id: 'container',
class: 'wrapper'
},
seen: true,
eventName: 'focus',
attrName: 'title',
attrValue: '详情请咨询百度...',
url: 'http://www.baidu.com'
}
}
}
</script>
响应式基础
声明响应式状态
响应式代理 vs 原始值
export default {
data() {
return {
someObject: {}
}
},
mounted() {
const newObject = {}
this.someObject = newObject
console.log(newObject === this.someObject) // false
}
}
声明方法
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
// 在其他方法或是生命周期中也可以调用方法
this.increment()
}
}
计算属性
computed: {
publishedBooksMessage() {
return this.author.books.length > 0 ? 'YES' : 'NO';
}
}
计算属性缓存 vs 方法
计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。
下面的 now 永远也不会更新,因为 Date.now() 不是一个响应式依赖
computed: {
now() {
return Date.now()
}
}
可写计算属性
Class 与 Style 绑定
绑定 HTML class
绑定内联样式
条件渲染
v-if
vs. v-show
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
列表渲染
v-for
事件处理
内联事件处理器 事件被触发时执行的内联 JavaScript 语句 (与 onclick
类似)。
方法事件处理器 一个指向组件上定义的方法的属性名或是路径。
<!-- 单击事件将停止传递 -->
<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 // macOS 上是 Command 键, windows 上是 win 键
.exact
修饰符
.exact
修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
表单输入绑定
v-model="message"
生命周期
侦听器
watch
默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果 想侦听所有嵌套的变更,你需要深层侦听器:
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// 注意:在嵌套的变更中,
// 只要没有替换对象本身,
// 那么这里的 `newValue` 和 `oldValue` 相同
},
deep: true
}
}
}
模板引用
<input ref="input">
组件基础
在 components 中注册
深入组件
组件注册
全局注册
app.component()
局部注册
使用 components 选项
<script>
import ComponentA from './ComponentA.vue'
export default {
// 注册 ComponentA,ComponentB 组件
components: {
ComponentA,
// key 名就是注册的组件名
CB : ComponentB
}
}
</script>
<template>
<ComponentA />
</template>
Props
一个组件需要显示的声明它所接受的 props ,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute 。
export default {
// props: ['foo'],
props : {
// key 是 prop 的名称, value 是 prop 的类型(构造函数)
title : String,
likes : Number,
greetingMessage : String
},
created() {
// props 会暴露到 `this` 上
console.log(this.foo)
}
}
<MyComponent gretting-message="hello" />
props 校验
export default {
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
}
}
组件事件
事件参数 声明触发的函数 事件校验
组件 v-model
v-model
在原生元素上实现双向绑定。
<input v-model="searchText" />
// 等效于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
v-model
在组件上实现双向绑定
透传 Attribute
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者
v-on
事件监听器。最常见的例子就是class
、style
和id
。
深层组件继承
禁用 Attributes 继承
inheritAttrs: false
通过 $attrs 可以访问到透传过来的 Attributes
- 保留原始大小写
@click
被暴露为$attes.onClick
函数
多根节点的 Attributes 继承
在 JavaScript 中访问透传 Attributes
插槽 Slots
渲染作用域
默认内容
具名插槽
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
插槽同时访问父组件和子组件的数据。
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
具名作用域插槽
高级列表组件
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>
无渲染组件
依赖注入
Props 逐级透传问题
provide
、inject
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
},
data() {
return {
// 基于注入值的初始数据 fullMessage: this.message
}
}
}
异步组件
Promise
组合式函数
响应式基础
面试题
2、v-show 与 v-if 的区别
- v-show 无论初始化条件是什么,元素都会被渲染
- v-show 是通过 css 隐藏元素,v-if 是直接将整个 dom 删除。
3、Vue 实例挂载的过程
new Vue(options) 发生了什么?
会调用 _init 方法
- 定义方法、事件、生命周期 调用 $mount 进行页面挂载 mountComponent 方法 执行 render 生成虚拟 DOM _update 将虚拟 DOM 生成真实 DOM 结构,并且渲染到页面。
v-for 与 v-if 的优先级
v-for 的优先级比 v-if 高