简介
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合
所有框架的逻辑 都是通过JS封装,上层概念比如MVVM, 方便快速开发,因此学习任何框架前都需要学习Web基础:
- HTML
- CSS
- JavaScript
在学习之前,快速浏览下官网: vue3官网, 阅读完简介部分
快速上手
我们先快速初始化一个 Vue3 的项目, 让我们对 Vue 有一个感性的认识
安装
$npm init vue@latest
Need to install the following packages:
create-vue@3.2.2
Ok to proceed? (y) y
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue3_example //项目名称
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
Scaffolding project in C:\Users\Desktop\web\vue3-example...
Done. Now run:
cd vue3_example
npm install 将依赖项安装在本地node_modules文件夹中
npm run lint 修复错误的配置
npm run dev 跑项目
经过 npm run dev 后, 本地构建工具 vite 会启动一个服务器, 将 Vue 相关语法编译为 HTML 相关语法, 从而让浏览器可以正常显示:(成功运行)
Vue Devtools
在使用 Vue 时,我们推荐在你的浏览器上安装 Vue Devtools。它允许你在一个更友好的界面中审查和调试 Vue 应用
工程目录结构
通过 Vue 脚手架搭建一个 Vue 项目,会自动生成一系列文件,而这些文件具体是怎样的结构、文件对应起什么作用,可以看看下面的解释
├── dist/ # 项目构建后的产物
├── node_module/ #项目中安装的依赖模块
├── public/ # 纯静态资源, 入口文件也在里面
|── src/
│ ├── stores # vue状态管理模块
│ │ └── counter.js # counter状态管理样例
│ ├── router # vue页面路由管理
│ │ └── index.js # 入口路由配置
│ ├── views # vue页面
│ │ ├── AboutView.vue # About页面
│ │ └── HomeView.vue # Home页面
│ ├── components/ # 组件
│ │ └── ...
│ ├── assets/ # 资源文件夹,一般放一些静态资源文件, 比如CSS/字体/图片
│ │ └── ...
│ ├── main.js # 程序入口文件
│ └── App.vue # 程序入口vue组件, 大写字母开头,后缀.vue
├── vite.config.js # vite构建配置
├── .gitignore # 用来过滤一些版本控制的文件,比如node_modules文件夹
├── package-lock.json # 执行完npm install后, 记录的当前想起使用的依赖的具体版本
├── package.json # 项目文件,记载着一些命令和依赖还有简要的项目描述信息
├── index.html # html入口文件
└── README.md #介绍自己这个项目的,可参照github上star多的项目。
Vue 部署
在vite.config.js文件中加入 base 这行内容,若已有则忽略
使项目 build 后找到正确的静态资源路径
$ npm run build ## 会在项目的dist目录下生成html文件, 使用这个静态文件部署即可
// 比如我们使用python快速搭建一个http静态站点
$ cd dist
$ python -m http.server
若还是没有画面则打开注册表编辑器
将 HKEY_CLASSES_ROOT.js 和 HKEY_LOCAL_MACHINE\SOFTWARE\Classes.js的 Content Type修改为 text/javascript
成功运行
MVVM如何诞生
现在主流的前端框架都是 MVVM 模型, MVVM 分为三个部分:
M(Model,模型层 ): 模型层,主要负责业务数据相关, 对应Vue中的 data 部分V(View,视图层): 视图层,顾名思义,负责视图相关,细分下来就是html+css层, 对应于Vue中的模板部分VM(ViewModel, 控制器): V 与 M 沟通的桥梁,负责监听 M 或者 V 的修改,是实现 MVVM 双向绑定的要点, 对应 Vue 中双向绑定
Vue 就是这种思想下的产物, 但是要讲清楚这个东西,我们不妨来看看 web 技术的进化史
CGI时代
最早的 HTML 页面是完全静态的网页,它们是预先编写好的存放在 Web 服务器上的 HTML 文件, 浏览器请求某个 URL 时,Web 服务器把对应的 HTML 文件扔给浏览器,就可以显示 HTML 文件的内容了
如果要针对不同的用户显示不同的页面,显然不可能给成千上万的用户准备好成千上万的不同的 HTML文件,所以,服务器就需要针对不同的用户,动态生成不同的 HTML 文件。一个最直接的想法就是利用 C、C++ 这些编程语言,直接向浏览器输出拼接后的字符串。这种技术被称为 CGI:Common Gateway Interface
下面是一个python的 CGI 样例:
后端模版时代
很显然,像新浪首页这样的复杂的 HTML 是不可能通过拼字符串得到的, 于是,人们又发现,其实拼字符串的时候,大多数字符串都是 HTML 片段,是不变的,变化的只有少数和用户相关的数据, 所以我们做一个模版出来,把不变的部分写死, 变化的部分动态生成, 其实就是一套模版渲染系统, 其中最典型的就是:
- ASP: 微软, C#体系
- JSP: SUN, Java体系
- PHP: 开源社区
下面是一段PHP样例:
但是,一旦浏览器显示了一个 HTML 页面,要更新页面内容,唯一的方法就是重新向服务器获取一份新的 HTML 内容。如果浏览器想要自己修改 HTML 页面的内容,怎么办?那就需要等到1995年年底,JavaScript 被引入到浏览器
有了 JavaScript 后,浏览器就可以运行 JavaScript ,然后对页面进行一些修改。JavaScript 还可以通过修改 HTML 的 DOM 结构和 CSS 来实现一些动画效果,而这些功能没法通过服务器完成,必须在浏览器实现
JavaScript原生时代
<p id="userInfo">
姓名:<span id="name">Gloria</span>
性别:<span id="sex">男</span>
职业:<span id="job">前端工程师</span>
</p>
有以上 HTML 片段,想将其中个人信息替换为 alice 的,我们的做法
// 通过ajax向后端请求, 然后利用js动态修改展示页面
document.getElementById('name').innerHTML = alice.name;
document.getElementById('sex').innerHTML = alice.sex;
document.getElementById('job').innerHTML = alice.job;
jQuery在这个时代脱颖而出
<div id="name" style="color:#fff">前端你别闹</div> <div id="age">3</div>
<script>
$('#name').text('好帅').css('color', '#000000'); $('#age').text('666').css('color', '#fff');
/* 最终页面被修改为 <div id="name" style="color:#fff">好帅</div> <div id="age">666</div> */
</script>
在此情况下可以下 前后端算是分开了, 后端提供数据, 前端负责展示, 只是现在 前端里面的数据和展示分开,不易于维护
前端模版时代
在架构上前端终于走上后端的老路: 模板系统, 引擎就是 ViewModel 动态完成渲染
<script id="userInfoTemplate">
姓名:<span>{name}</span>
性别:<span>{sex}</span>
职业:<span>{job}</span>
</script>
var userInfo = document.getElementById('userInfo');
var userInfoTemplate = document.getElementById('userInfoTemplate').innerHTML;
userInfo.innerHTML = templateEngine.render(userInfoTemplate, users.alice);
虚拟DOM技术
用我们传统的开发模式,原生 JS 或 JQ 操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程, 操作 DOM 的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验
如何才能对 DOM 树做局部更新,而不是全局更新喃? 答案就是由 JS 动态来生产这棵树, 修改时动态更新, 这就是虚拟 DOM
这是一个真实 DOM
这是动态生成的 DOM 是不是和 CGI 很像
组件化时代
MVVM 最早由微软提出来,它借鉴了桌面应用程序的 MVC 思想,在前端页面中,把 Model 用纯 JavaScript 对象表示,View 负责显示,两者做到了最大限度的分离。
结合虚拟 DOM 技术, 我们就可以动态生成 View , 在集合 MVVM 的思想, 前端终于迎来了组件化时代
- 页面由多个组件构成
- 每个组件都有自己的
MVVM
下面这个页面就是由多个 Vue 组件构成的:
Vue与MVVM
本节与下节有些没出现过的代码不明白没关系,后面会讲
-
Model: Vue 中用于标识 model 的数据是 data对象, data 对象中的所有的 property 加入到 Vue 的响应式系统中, 由 Vue 监听变化 -
View: Vue 使用模板来实现展示, 但是渲染时要结合vdom技术 -
ViewModle: Vue 的核心, 负责视图的响应, 也就是数据双向绑定- 监听
view中的数据, 如果数据有变化, 动态同步到data中 - 监听
data中的数据, 如果数据有变化, 通过vdom动态渲染视图
- 监听
比如我们修改下About组件(即 vue-project/src/views/AboutView.vue), 在model 中添加一个 name 属性
然后在模板中添加个输入框来修改他, 给他 h1 展示 name 属性
默认情况下vue是单向绑定: 数据 --> 视图
如果要双向绑定, 需要使用v-model: 视图 <--> 数据
选项式 API和组合式 API
在上面的例子中 我们通过 export default {} 暴露出一个 Vue 的实例(先不要纠结什么是 Vue 实例, 我们马上就要讲到), 我们可以把 export 出去的这个对象认为是 Vue 实例的配置
Vue 提供了2种方式来配置 Vue 实例, 被叫做两种 API 风格, 他们都能够覆盖大部分的应用场景。
它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API 也是用组合式 API 实现的!关于 Vue 的基础概念和知识在它们之间都是通用的
选项式 API
我们上面使用的就是选项式API, 使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data 、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例
比如我们在 about 页面,添加一个 mounted 选项(当界面模板渲染完成后调用,可以切换 Home 和 About 进行显示), 打印下 this (当前组件实例)
<script>
export default {
name: "HelloWorld",
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
name: "lfd",
};
},
props: {
msg: String,
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(this);
},
};
</script>
由此可以选项式 API 就是 Vue 实例(或者叫组件的实例)给我们留的钩子, 用于我们控制 Vue 实例的行为。
组合式 API
组合式API 为我们提供了另外一种设置 Vue 实例的方式: 直接在函数作用域内定义响应式状态变量, 这个变量可以是数据, 比如 [], {},... 也可以是函数 比如 search() ...
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑
<script setup>
// 以库的形式来使用vue实例提供的API
import { ref, onMounted, getCurrentInstance } from "vue";
// 响应式状态
// 相当于 data里面的 name属性
const name = ref("");
// 使用构造函数onMounted
// 想到于mounted选项
onMounted(() => {
// 通过getCurrentInstance获取到当前组件的vue实例
const _this = getCurrentInstance();
console.log(_this);
});
</script>
Vue实例
我们知道可以通过 getCurrentInstance 获取当前 Vue 实例, 不要被内部的细节吓到, 我们只是大体上认知下 Vue 实例上的一些关键属性, 好方便有个全局意识, 在后面的细节讲解中 会对他们有详细解释.
下面是关于该实例的描述:
内部实例一般是给库或者框架开发者预留的, 属于底层扩展,比如操作虚拟DOM, 访问响应式数据, 而留给 Vue 使用者的实例是ComponentPublicInstance, 比如定义实例的一些属性(props, slots)和一些钩子函数的定义(mounted,...):
创建实例
我们如何创建一个Vue实例? 我们看看入口: main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
const app = createApp(App); // 创建实例
app.use(createPinia());
app.use(router);
app.mount("#app");
根组件
每个应用都需要一个“根组件”,其他组件将作为其子组件
App (root component)
├─ TodoList
│ └─ TodoItem
│ ├─ TodoDeleteButton
│ └─ TodoEditButton
└─ TodoFooter
├─ TodoClearButton
└─ TodoStatistics
这里传入的就是一个根组件: App.vue , 也就是 vue Root 实例, 也就是 MVVM 里面的 ViewModel 概念的实体
挂载应用
应用实例必须在调用了 mount() 方法后才会渲染出来
// 该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串
// 这里的参数是一个 css id选择器, 他对应着一个HTML的元素
app.mount('#app')
npm run build 后 会在 dist 目录下看到构建后的 HTML 产物:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<!-- 这就是编译后压缩 的纯JS脚步, 他的作用就是动态操作DOM(VDOM) -->
<script type="module" crossorigin src="/assets/index.454cea7e.js"></script>
<link rel="stylesheet" href="/assets/index.f6f45cab.css">
</head>
<body>
<!-- js 框架(vue) 作用的根元素, 后期就是靠 vue框架生产虚拟DOM来动态选择出界面的 -->
<div id="app"></div>
</body>
</html>
应用配置
应用配置就是全局配置, 当你有一些配置想要对全局生效, 你应该要能想起他
上面的 use 就是用于加载全局插件使用的, 后面我们加载UI插件也会使用到 use
app.use(createPinia());
app.use(router);
Vue实例生命周期
上面我们使用过一个onMounted的函数:
// 以库的形式来使用vue实例提供的API
import { onMounted } from "vue";
// 使用构造函数onMounted
onMounted(() => {
// 通过getCurrentInstance获取到当前组件的vue实例
const _this = getCurrentInstance();
console.log(_this);
});
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如挂载实例到 DOM(onMounted)。在此过程中,它也会运行称为生命周期钩子的函数,让开发者有机会在特定阶段添加自己的代码
下面我们使用组合式API来调试下这些生命周期钩子:
<script setup>
// 以库的形式来使用vue实例提供的API
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
// 响应式状态
// 相当于 data里面的 name属性
const name = ref("lfd");
onBeforeMount(() => {
console.log("before mount");
});
onMounted(() => {
console.log("mounted");
});
onBeforeUpdate(() => {
console.log("before update");
});
onUpdated(() => {
console.log("on updated");
});
onBeforeUnmount(() => {
console.log("before unmount");
});
onUnmounted(() => {
console.log("unmounted");
});
</script>
下面是调试结果:
- 切换至Home组件时输出
unmount - 切换回About组件时输出
mount - 在input栏更改信息时输出
update
下面是选项式API:
<script>
export default {
name: "HelloWorld",
data() {
return {
name: "lfd",
};
},
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeUnmount() {
console.log("beforeDestroy");
},
unmounted() {
console.log("destroyed");
},
props: {
msg: String,
},
};
</script>
下面是控制台的日志
# 当我们点到About页面时, vue实例初始化
beforeCreate
created
beforeMount
mounted
# 当我们离开该页面时, vue实例销毁
beforeDestroy
destroyed