该系列文章主要基于官方Vue教程,对Vue使用及特性等方面做一个较为系统的总结

官方文档链接Vue.js


Vue概述

框架的作用

vue是一套构建用户界面的javascript框架,基于html,css,javascript构建,提供一套声明式的,组件化的编程模型。

前端开发框架的本质作用是简化开发,其优势是提供了一系列模块化语法,只需遵守这些语法就可以涵盖大量日常开发的需求,使用开发框架具有如下特点:

  • 组件化开发:将代码拆分为独立的、可复用的组件,总而便于管理
  • 状态管理:提供强大的数据管理流,从而帮助开发者管理数据状态
  • 虚拟DOM:使用虚拟DOM构建DOM树,能够减少实际对DOM的操作次数,从而提高开发效率
  • 响应式数据绑定:提供一系列响应式数据绑定api,实现数据与视图的同步
  • 单页应用(SPA):使用框架非常适合构建单页应用,可以在不刷新页面的前提下实现页面更新

vue是一个框架,也是一个生态,符合大部分前端开发需求。

渐进式框架

什么是渐进式框架?

渐进式框架即在开发过程中可以被渐进集成的框架,这意味着Vue项目将不需要在开始就规划好所有功能,而是可以慢慢根据需求进行集成,这对于逐渐膨胀的前端开发而言是非常有利的

渐进式框架的特点

  1. 无需构建步骤,渐进式增强静态html
  2. 在任何页面中作为web components嵌入
  3. 单页应用(SPA
  4. 全栈、服务端渲染(SSR
  5. jamestack、静态站点生成(SSG
  6. 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

使用Vue可以渐进式地集成上述功能

Vue代码风格

选项式api与组合式api

两种风格的api可以实现相同的功能,但是语言风格不同

选项式api适合解决需要不断拓展的需求

组合式api更适合解决完整的单页开发

两种api底层系统相同,都能解决大部分问题。

但两种api都有一个共性,即Vue项目不关注dom是怎么变化的,而是关注数据,并将数据同步到视图中

Vue实例创建

创建一个vue应用

主流有两种脚手架工具,分别是vue-clivite,相对来说vite的构建效率要大于vue-cli,不过开发中两者都很常见

  1. 构建Vue项目
    使用vue-cli构建
    1
    vue create my-new-vue-project
    使用Vite构建
    1
    npm create/init vue@latest
  2. 安装依赖,启动服务器
    1
    2
    npm install
    npm run serve/dev
  3. 发布到生产环境
    1
    npm run build

构建一个vue实例

如果直接使用CDN而不是npm创建vue实例,可以直接通过url引入createApp,这种方式一般用于对html做静态提升

在早期也有通过new Vue创建Vue实例的,但引入了createApp后更倾向于这种方式

  1. 准备一个根模板容器
    1
    2
    3
    <div id="app">
    <button @click="count++">{{ count }}</button>
    </div>
  2. 创建导入构造对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { createApp } from 'vue'

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

  3. 挂载构造对象
    1
    app.mount('#app')

一个页面可以创建多个vue实例,如果你正在使用Vue来增强服务端渲HTML,并且只想要Vue去控制一个大型页面中特殊的一小部分,应避免将一个单独Vue应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

Vue核心概念

Vue构建网页的具体流程如下:

  1. 实例创建:通过createApp创建一个Vue实例
  2. 选项注入:使用选项或组合式api注入数据和方法
  3. 创建虚拟DOM:模板编译生成虚拟DOM
  4. 挂载:将虚拟DOM树挂载到真实DOM树上
  5. 渲染:数据变化时,通过DOM diff算法更新真实DOM

选项注入

创建Vue实例后,下一步便是对选项进行注入,Vue2的选项式api即将数据抽象为具体的如data,computed,method等选项或方法进行注入Vue实例中
Vue3中则是将数据封装进setup方法中,通过ref,reactive等来定义响应式状态

虚拟DOM

vue模板并不是真实的DOM,而是虚拟DOM,虚拟DOM本质上是一个字符串,vue内部会根据虚拟DOM生成真实DOMvnode树),这个过程被称为vue渲染,通过虚拟DOM节点的对比修改数据,从而提高效率,vue渲染使用render()函数的返回值生成vnode,从而创建真实DOM

虽然Vue2Vue3整体构建方法类似,但Vue3中通过算法的优化大大提升了构建效率

Vue特性

插值表达式

最基本的数据绑定形式,响应式双大括号中的值

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

样式作用域 scoped

通过hash映射对组件样式进行约束

1
<style scoped>

样式作用域不仅影响当前组件,还会影响子组件的根元素

深层选择器 :deep()

处于scoped样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,可以使用:deep()这个伪类:

1
2
3
4
5
<style scoped>
.a :deep(.b) {
/* ... */
}
</style>

组件递归

一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用 <FooBar/> 引用它自己

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Comment.vue -->
<template>
<div>
<p>{{ comment.text }}</p>
<div v-if="comment.replies">
<Comment v-for="reply in comment.replies" :key="reply.id" :comment="reply" />
</div>
</div>
</template>

<script>
export default {
name: 'Comment',
props: {
comment: {
type: Object,
required: true
}
}
};
</script>

动态参数

动态参数允许你在模板中动态绑定事件或者方法,使用动态参数可以做到动态修改属性名!(不是属性的值),具体实现方式是使用[]来绑定属性名

1
2
3
4
5
6
7
8
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

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

还可以绑定方法名
1
2
3
4
<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>

使用动态参数时,需保证表达式的值为一个字符串且不支持null,此外,空格可引号都是不被允许的

Vue基本指令

v-html/v-text

  • v-text用以动态设置元素文本内容
  • v-html用以动态设置元素innerhtml,包括DOM元素
1
2
3
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>
1
<div v-html="html"></div>

v-show/v-if

  • 两者都用来控制元素的显示与隐藏
  • v-if是真实地按条件渲染,即”真实地”控制元素的重建与销毁
  • v-show则只是相当于控制元素的display属性为none,相当于从页面中移除,在DOM树中仍保有一席之地
  • 因此,v-show用来控制需要经常变动的元素,而v-if处理变动较少的元素
1
2
3
4
5
6
7
8
9
10
11
12
<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>
1
<h1 v-show="ok">Hello!</h1>

还有一个老生常谈的问题了,就是v-if不能和v-for在一起使用,由于v-ifv-for的优先级更高。这意味着v-if的条件将无法访问到v-for作用域内遍历的值

v-on

  • 我们可以使用v-on指令 (简写为 @) 来监听DOM事件,并在事件触发时执行对应的 JavaScript
  • v-on可以处理内联事件或方法事件
1
2
3
const count = ref(0)
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
1
2
3
4
5
6
7
8
9
10
11
const name = ref('Vue.js')

function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}

<button @click="greet">Greet</button>
  1. .stop:单击事件时停止传递
  2. .prevent:提交表单时不会刷新页面,相当于event.preventDefault()
  3. .capture:当事件涉及子组件穿透时优先处理父组件
  4. .self:当事件的event.target指向自身(不来自子元素)时才触发
  5. .once:点击事件最多触发一次
  6. .passive:保证滚动事件的默认行为立即发生

修饰符遵从链式调用原则

  1. vue为一些系统按键提供了别名,如.enter,.delete,.space,.esc,.space,.up,.down,.left,.right,.ctrl,.alt,.shift
    1
    2
    3
    4
    5
    <!-- Alt + Enter -->
    <input @keyup.alt.enter="clear" />

    <!-- Ctrl + 点击 -->
    <div @click.ctrl="doSomething">Do something</div>
  2. .exact修饰符:可使用.exact修饰符来指定当单独按键被按下时才触发事件
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 当按下 Ctrl 时,即使同时按下 AltShift 也会触发 -->
    <button @click.ctrl="onClick">A</button>

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

    <!-- 仅当没有按下任何系统按键时触发 -->
    <button @click.exact="onClick">A</button>
  3. 鼠标按键修饰符.left,.right,.middle

v-bind

  • v-bind,简写为:,在vue中用作数据绑定
  • 常用作绑定类和内联样式或者对vue内置参数进行绑定,如keyprops
  • v-bind可以绑定数组,对象,支持一次绑定多个参数
1
2
3
4
5
6
7
8
9
10
11
const isActive = ref(true)
const hasError = ref(false)

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

//等同于

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

v-for

  • v-for用于基于数组来渲染一个列表
  • 模板语法: v-for = "(value,key?,index) in items"
  • 参数示意:value表示数组的值, key(可选)表示数组的键,index(可选)表示数组的索引
  • v-for绑定一个唯一的:key来确保跟踪每个项
1
2
3
4
5
6
7
8
9
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>

v-model

  • v-model本质是一个语法糖,用于简化将表单内容同步到Javascript中的相应变量这一步骤
  • v-model会绑定文本类型,如<input>,<textarea>value属性,并监听input事件
  • v-model会根据文本类型自动调整绑定方式,例如,对ckeckbox可以绑定一个数组,用以存储所有多选的值
1
2
3
4
5
6
7
<input
:value="text"
@input="event => text = event.target.value">

//将等同于

<input v-model="text">
1
2
3
4
5
6
7
8
9
10
11
12
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>
  1. .lazy:v-model会在每次change事件触发后(文本框失去焦点,多选框选择等)更新数据
  2. .number:把输入自动转化为数字,会在输入框有type=number时自动启用
  3. .trim:自动去除用户输入两端的空格

自定义指令

  • 除了Vue内置的一系列指令外,Vue还允许你注册自定义的指令
  • <script setup>语法糖中,任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令,如vFocus即可以在模板中以 v-focus的形式使用
  • 在没有<script setup>中,可以配置derective配置项定义自定义指令
  • 可以使用app.derective()全局定义指令
  • 自定义指令由一个包含生命周期钩子对象组成,预定义的钩子函数触发,根据内含的参数创建方法
方法一(script setup)
1
2
3
4
5
6
7
8
9
10
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>

<template>
<input v-focus />
</template>
方法二(配置项)
1
2
3
4
5
6
7
8
9
10
11
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
方法三(全局注册)
1
2
3
4
5
6
const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})

vue2中会使用insert()表示绑定,用update()表示更新,这些在vue3中被替换成了生命周期钩子,便于记忆


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}

为简化自定义指令,由于大部分情况下自定义指令由mountedupdated触发,故简化形式可直接传入一个函数表示这两种情况


1
2
3
4
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})

  • el:指令绑定到的元素。这可以用于直接操作DOM
  • binding:一个对象,包含以下属性。
    • value:传递给指令的值。例如在v-my-directive=”1 + 1” 中,值是 2。
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。

  • prevVnode:代表之前的渲染中指令所绑定元素的VNode。仅在 beforeUpdateupdated 钩子中可用。

    1
    2
    3
    4
    5
    6
    7
    8
    <div v-example:foo.bar="baz">
    //对于上述指令,binding参数为如下对象
    {
    arg: 'foo',
    modifiers: { bar: true },
    value: /* `baz` 的值 */,
    oldValue: /* 上一次更新时 `baz` 的值 */
    }