Vue3基础入门
1 Vue简介
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
2 前提条件
(1)熟悉命令行操作;
(2)安装Node.js 18.3 或更高版本;
3 创建项目
查看Node.js的版本,确保版本符合条件:
npm create vue@latest
这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示:
暂时全选No,日后需要某个依赖包,在使用npm install命令安装依赖即可。
4 启动项目
首先先执行cd命令跳转到项目所在根目录,然后执行npm install命令安装项目所需必要依赖:
最后执行命令启动项目(该命令表示启动dev开发环境的项目):
npm run dev
访问http://localhost:5173/,见到如下欢迎页面说明项目创建并启动成功:
5 目录结构
vue相关代码都在src目录下,通过vite项目构建工具打包后,转化为浏览器能识别的文件格式:html、css、js,通过index.html页面可以看到打包后的页面。
项目管理文件保存相关依赖的版本信息和开发环境信息。下载的第三方模块保存在node_modules。
6 单文件组件
Vue 的单文件组件 (即 *.vue
文件,英文 Single-File Component,简称 SFC) 是一种特殊的文件格式,使我们能够将一个 Vue 组件的模板、逻辑与样式封装在单个文件中。下面是一个单文件组件的示例:
6.1 单文件组件组成
Vue 的单文件组件是网页开发中 HTML、CSS 和 JavaScript 三种语言经典组合的自然延伸。<template>、<script> 和 <style>三个块在同一个文件中封装、组合了组件的视图、逻辑和样式。
每个单文件组件的三种标签<template>、<script> 和 <style>只能各有一个顶层的标签。
6.1.1 <template>标签
<template>使用HTML语言,描述了页面的结构。
在模板中可以使用vue的模板语法进行绑定数据、处理事件以及定义组件的DOM结构等。
6.1.2 <script>标签
<script>使用JavaScript或TypeScript代码,描述了组件的行为逻辑。
<script setup>专为组合式api设计的一种特殊的语法糖,它允许你在一个更简洁、更直观的方式下编写组件逻辑。
这个脚本块将被预处理为组件的setup()函数,这意味着它将为每一个组件实例都执行。<script setup>中的顶层绑定都将自动暴露给模板。
6.1.3 <style>标签
每个*.vue 文件可以包含多个 <style>标签。
通常使用CSS或CSS预处理器编写样式。定义了组件的样式和布局,用于控制组件的外观和样式。
6.2 导入组件
而一个vue项目,所有的单文件组件均属于同一个根组件App.vue,其它单文件组件可以作为其子组件一同被挂载到DOM树下。
比如,在App.vue组件中导入HelloWorld组件和TheWelcome组件:
而HelloWorld组件和TheWelcome组件作为子组件,App.vue组件作为父组件,父组件就可以使用子组件。
6.3 挂载应用
为什么创建好项目后我们可以看到欢迎页面,但是index.html页面并没有很多信息,而欢迎页面却有很多信息?这是因为初始化项目时,Vue已经自动将一些组件挂载到index.html的DOM树上(代码位于main.js中):
上图中,从vue包中导入createApp函数,传入createApp的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。这表示创建一个App以及其子组件的一个实例。
但是只有.mount()函数执行,该实例才能真正渲染到DOM树上。该函数接受一个实际的 DOM 元素或是一个CSS选择器字符串,比如上图接受id选择器为app的DOM元素,即下图的<div id="app">这个容器的内容是App组件以及其子组件:
现在观察index.html,就会发现实际上通过script标签引入main.js,main.js又将App.vue组件以及其子组件挂载到div容器id为app的容器中,从而使index.html页面显示出组件的内容。
这种单文件组件的好处如下:
- 使用熟悉的HTML、CSS和JavaScript语法编写模块化的组件
- 让本来就强相关的关注点自然内聚
- 预编译模板,避免运行时的编译开销
- 组件作用域的CSS
- 在使用组合式API时语法更简单
- 通过交叉分析模板和逻辑代码能进行更多编译时优化
- 更好的IDE支持,提供自动补全和对模板中表达式的类型检查
- 开箱即用的模块热更新(HMR)支持
7 选项式API与组合式API
7.1 选项式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>
7.2 组合式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>
7.3 如何选择
官方推荐:
(1)当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
(2)当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。
实际上,根据自己的喜好和理解程度即可,哪种自己觉得更好用更容易理解,就用哪种。
8 响应式数据
响应式数据是指当数据发生改变,能自动更新和通知与数据相关的组件或视图。
传统JavaScript代码实现数据变化修改视图很麻烦,需要获取到DOM元素,再通过事件函数来监听相关修改操作,最后将DOM元素的值进行修改。
而Vue的响应式数据则可以非常简单方面的实现上述操作:
8.1 ref()
ref()函数可以接收基本类型或对象类型作为参数,返回一个响应式的对象。其本质是在参数基础上嵌套一层对象,因此基本类型或对象类型的访问需要通过响应式对象的属性访问。
8.1.1 接收基本类型
基本类型作为ref()的参数,则返回的对象是RefImpl(即为响应式对象),该对象的value属性才是原来的基本类型数据,因此访问时需要通过.value来访问。
而响应式对象被渲染到template中,并没有通过.value来访问值,这是因为Vue会自动解包,即模板中的响应式数据会自动去除外面那层RefImpl对象,从而直接访问到数据值:
<template><button @click="increment">Count is: {{ counter }}</button>
</template>
<script setup>
import { ref } from "vue";
// 基本类型作为响应式对象
const counter = ref(0);
console.log("counter: ", counter);
function increment() {counter.value++;console.log("count: ", counter.value);
}
</script>
上图是发生一次点击后,ref对象的value即为原来的基本类型的数据。
8.1.2 接收对象类型
对象类型作为ref()的参数,则返回的对象是RefImpl(即为响应式对象),该对象的value属性才是{count:0}对象,因此访问该对象的count属性时需要通过.value.count来访问。
而模板中,自动解包后,该对象就可以直接通过属性来访问:
<template><button @click="increment">Count is: {{ counter.count }}</button>
</template>
<script setup>
import { ref } from "vue";
// 对象类型作为响应式对象
const counter = ref({count: 0,
});
function increment() {counter.value.count++;console.log("count: ", counter.value.count);
}
</script>
8.1.3 ref()解包
(1)ref对象作为reactive对象的属性,访问reactive对象的ref对象属性会自动解包,访问到ref对象的value;而ref.value和reactive.ref会建立连接,修改reactive.ref通过ref.value访问也是响应式的:
const count = ref(0)
const state = reactive({count
})console.log(state.count) // 0state.count = 1
console.log(count.value) // 1
如果将一个新的ref赋值给一个关联了已有ref的属性,那么它会替换掉旧的ref:
const otherCount = ref(2)state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
(2)ref作为reactive的响应式数组对象或响应式原生集合对象(比如Map),通过访问数组元素或集合元素的方式ref对象不会自动解包:
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
(3)模板中只有顶级的ref对象才会被解包:
//顶级ref对象
const count = ref(0)
//object不是顶级ref对象,object.id才是顶级ref对象
const object = { id: ref(1) }
当template模板中渲染{{ count + 1 }},该元素是响应式的,显示为1;而渲染{{ object.id + 1 }},该元素不是响应式,而被直接渲染为[object Object]1。
解决办法是将object.id解构:
const { id } = object
此时template模板中渲染{{id + 1}}就是响应式的,显示为2。
注意:插值表达式中,ref是文本插值的最终值时(而不是再进行运算后显示),可以被自动解构。即{{ object.id }}是可以直接显示为1。
8.2 reactive()
8.2.1 接收对象类型
reactive()只能接收对象类型的参数作为响应式对象,并且其行为是对原始对象做了一个代理,而不是嵌套了一层对象。因此reactive()返回的是一个原始对象的Proxy,它和原始对象是不相等的,修改原始对象的属性值并不能影响响应式对象的属性值:
const raw = {}
const proxy = reactive(raw)// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
{count:0}对象作为响应式数据的参数,counter变量接收返回的响应式数据,由于没有再嵌套对象,因此对该对象的count属性访问不需要再使用.value,直接.count访问属性值即可:
<template><button @click="increment">Count is: {{ counter.count }}</button>
</template>
<script setup>
import { reactive } from "vue";
// reactive只能接受对象作为响应式数据
const counter = reactive({count: 0,
});
function increment() {counter.count++;console.log("count: ", counter.count);
}
</script>
8.2.2 缺点
(1)只能接收对象类型;
(2)不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失;
(3)对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接;
const counter = reactive({count: 0,
});
console.log(counter);
let {count} = counter;
count++;
console.log("counter.count: ", counter.count);
console.log("count: ", count);
由于使用{count}方式将counter的count属性解构,因此count变量丢失了响应式数据的连接,直接操作count并不会影响响应式数据counter的count属性值。
由于上述缺陷,因此更推荐使用ref(),因为ref()接受的变量类型全面,且对解构操作更友好。