Vue3快速入门学习笔记一
一、概要
1.1、库与框架的区别
框架是一个软件的半成品,在全局范围内给了大的约束。库是工具,在单点上给我们提供功能。框架是依赖库的。Vue是框架而jQuery则是库。
1.2、MVC(Model View Controller)
MVC的核心理念是:你应该把管理数据的代码(Model)、业务逻辑的代码(Controller)、以及向用户展示数据的代码(View)清晰的分离开
- 模型:代表应用当前的状态
- 视图:用于展示数据,用于接口
- 控制器:用来管理模型和视图之间的关系
典型思路是 View 层通过事件通知到 Controller 层,Controller 层经过对事件的处理完成相关业务逻辑,要求 Model 层改变数据状态,Model 层再将新数据更新到 View层。
View层和 Model 层相互持有、相互操作,导致紧密耦合,在可维护性上有待提升。由此,MVP 模式应运而生
通过MVC框架又衍生出了许多其它的架构,统称MV*,最常见的是MVP与MVVM
1.3、MVP(Model View Presenter)
MVP 模式将程序分为三个部分:模型(Model)、视图(View)、管理层(Presenter)。/prɪˈzentə(r)/
Model 模型层: 只负责存储数据,与 View 呈现无关,也与 UI 处理逻辑无关,发生更新也不用主动通知 View;
View 视图层: 人机交互接口,一般为展示给用户的界面;
Presenter 管理层 : 负责连接 Model 层和 View 层,处理 View 层的事件,负责获取数据并将获取的数据经过处理后更新 View;
MVC 模式的 View 层和 Model 层存在耦合,为了解决这个问题,MVP 模式将 View 层和 Model 层解耦,之间的交互只能通过 Presenter 层,实际上,MVP 模式的目的就是将 View 层和 Model 层完全解耦,使得对 View 层的修改不会影响到 Model 层,而对 Model 层的数据改动也不会影响到View 层。
典型流程是 View 层触发的事件传递到 Presenter 层中处理,Presenter 层去操作 Model 层,并且将数据返回给 View层,这个过程中,View 层和 Model 层没有直接联系。而 View 层不部署业务逻辑,除了展示数据和触发事件之外,其它时间都在等着 Presenter 层来更新自己,被称为「被动视图」。
由于 Presenter 层负责了数据获取、数据处理、交互逻辑、UI 效果等等功能,所以 Presenter 层就变得强大起来,相应的,Model 层只负责数据存储,而 View 层只负责视图,Model 和 View 层的责任纯粹而单一,如果我们需要添加或修改功能模块,只需要修改 Presenter 层就够了。由于 Presenter 层需要调用 View 层的方法更新视图,Presenter 层直接持有 View 层导致了 Presenter 对 View 的依赖。
正如上所说,更新视图需要 Presenter 层直接持有 View 层,并通过调用 View 层中的方法来实现,还是需要一系列复杂操作,有没有什么机制自动去更新视图而不用我们手动去更新呢,所以,MVVM 模式应运而生。
1.4、MVVM(Model View ViewModel)
MVVM 模式将程序分为三个部分:模型(Model)、视图(View)、视图模型(View-Model)。
和 MVP 模式类似,Model 层和 View 层也被隔离开,彻底解耦,ViewModel 层相当于 Presenter 层,负责绑定 Model 层和 View 层,相比于 MVP 增加了双向绑定机制。
MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。
Vue与Angular就是一个MVVM框架,MVVM与MVC最大的区别是模型与视图实现了双向绑定。
在Vue中用户自定义的实例就是vm,功能与Controller类似
1.5、Vue.js
Vue.js是一个轻巧、高性能、可组件化的MVVM库,同时拥有非常容易上手的API,作者是尤雨溪是中国人。
文档与资源大全:https://vue3js.cn/
易学易用
基于标准 HTML、CSS 和 JavaScript 构建,提供容易上手的 API 和一流的文档。
性能出色
经过编译器优化、完全响应式的渲染系统,几乎不需要手动优化。
灵活多变
丰富的、可渐进式集成的生态系统,可以根据应用规模在库和框架间切换自如。
当前三大前端MVC框架的对比:
1.5.1、Vue.js介绍
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
1.性能的提升
- 打包大小减少41%
- 初次渲染快55%, 更新渲染快133%
- 内存减少54%
2.源码的升级
- 使用Proxy代替defineProperty实现响应式
- 重写虚拟DOM的实现和Tree-Shaking
3.拥抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
- Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与 inject
- 新的内置组件
- Fragment
- Teleport
- Suspense
- 其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
1.5.2、Vue带来的新变化
1.性能提升
首次渲染更快、diff算法更快、内存占用更少、打包体积变小
2.更好的TypeScript支持
3.Composition API(重点)
在使用vue2.x版本开发较复杂的组件时,逻辑难以复用,组合式api的出现可以解决此类问题
相关阅读:
- Vue3 中文文档 https://vue3js.cn/docs/zh/
- Vue3 设计理念 https://vue3js.cn/vue-composition/ 破坏性语法更新
- 官网:https://vuejs.org/ https://cn.vuejs.org/
vue3.0对于2.0版本的大部分语法都是可以兼容的,但是也有一些破坏性的语法更新,需要格外注意
- 实例方法$on移除 (eventBus现有实现模式不再支持 可以使用三方插件替代)
- 过滤器filter移除 (插值表达式里不能再使用过滤器 可以使用methods替代)
- .sync语法移除 (和v-model语法合并)
1.6使用vue-cli构建第一个Vue程序
1.6.1、安装Node.js
有讲过(略)
查看版本
node -v
查看版本显示版本号则表示正常安装了node.js
1.6.2、安装Vue-cli
如果之前安装过Vue2,需要把Vue2卸载。
npm uninstall vue-cli -g
安装最新版本的vue-cli
npm i -g @vue/cli
查看版本号
vue -V
1.6.3、创建Vue3.0项目
1.打开要创建项目的目录文件,进入命令行
2.输入创建项目的命令
vue create 项目名称
项目名中不能包含大写字母
使用空格键可以选择,使用上下键可以移动,各项的意义如下:
Babel:将源代码转换成指定版本的JS,如ES6=>ES5
TypeScript:使用强类型的JavaScript预处理语言
PWA:使用渐进式网页应用
Router:使用Vue路由
Vuex:使用Vuex状态管理
CSS Pre-processors:CSS预处理器,如Less、Sass
Linter/Formatter:使用代码风格检查和格式化器
Unit Testing:使用单元测试
E2E Testing:使用一种End to End (端到端)的黑盒测试
选择Vue版本,这里直接选择3
是否使用Class风格的组件定义语法?
即原本是:home = new Vue()创建vue实例
使用装饰器后:class home extends Vue{}
这里选择 y
使用Babel与TypeScript一起用于自动检测的填充? y
是否使用history路由模式,如果启用,则项目生成之后有可能会出现打开浏览器页面是空白,这里选择n
ESLint with error prevention only (仅具有错误预防功能)
ESLint + Airbnb config (Airbnb配置)
ESLint + Standard config (标准配置)
ESLint + Prettier (更漂亮)
Lint on save(保存时检查)
Lint and fix on commit(提交时检查)
Babel,ESLint等配置文件的存放方式
In dedicated config files 存放到独立文件中
In package.json 存放到 package.json 中
这里选择默认项In dedicated config files
是否需要保存当前配置,为以后生成新项目时进行快速构建,这里选择n,不保存。
然后等待
1.6.4、运行项目
使用cd命令进入项目,然后运行
1.7、使用图形化界面创建项目
你也可以通过 vue ui
命令以图形化界面创建和管理项目
vue ui
上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。
1.8、使用Vite构建第一个Vue程序
官方文档:v3.cn.vuejs.org/guide/insta…
vite官网:vitejs.cn
-
什么是vite?—— 是Vue团队打造的新一代前端构建工具。
-
优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
-
传统构建 与 vite构建对比图
而Vite有点按需加载的意思在那里了~
接下来我们就用Vite来创建一个Vue3的项目
## 创建工程 npm init vite-app 项目名(demo1) ## 进入工程目录 cd demo1 ## 安装依赖 npm install ## 运行 npm run dev
构建速度明显vite快
运行
二、单文件组件和API风格
2.1、单文件组件
在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为 *.vue
文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。下面我们将用单文件组件的格式重写上面的计数器示例:
<script> export default { data() { return { count: 0 } } } </script> <template> <button @click="count++">Count is: {{ count }}</button> </template> <style scoped> button { font-weight: bold; } </style>
单文件组件是 Vue 的标志性功能。如果你的用例需要进行构建,我们推荐用它来编写 Vue 组件。你可以在后续相关章节里了解更多关于单文件组件的用法及用途。但你暂时只需要知道 Vue 会帮忙处理所有这些构建工具的配置就好。
2.2、API 风格
Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。
2.2.1、选项式 API (Options 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>
2.2.2、组合式 API (Composition API)
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup
attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
下面是使用了组合式 API 与 <script setup>
改造后和上面的模板完全一样的组件:
<template> <div> <button type="button" @click="increment()">count is : {{count}}</button> </div> </template> <script> import { ref, onMounted } from 'vue' export default { name: 'Hello', setup () { const count = ref(0) function increment () { count.value++ } onMounted(() => { console.log(count) }) return { count, increment } } } </script> <style scoped> button{ font-weight: bold; } </style>
三、下载VS Code 集成开发工具(IDE)
VS code,全称Visual Studio Code,是Microsoft(微软)在2015年4月30日发布的,编写现代web和跨平台源代码编辑器。
官网:https://code.visualstudio.com/Download
根据自己电脑的系统和配置选择对应版本即可
四、下载安装vscode插件
4.1、插件Vue 3 Snippets(使用它就不能使用vue2)
作用:用于vue3的智能代码提示,语法高亮、智能感知、Emmet等。替代Vetur插件,Vetur在vue2时期比较流行。
常用命令:vueinit、v3等
4.2、插件Volar
作用:语法高亮、智能感知、Emmet等
4.3、插件ESLint(使用它以后很折磨人)
作用:检查代码是否符合规范
4.4、插件Prettier
作用:代码格式化
4.5、其他一些常用插件
Auto Rename Tag 修改 html 标签,自动帮你完成尾部闭合标签的同步修改,和 webstorm 一样
Auto Close Tag 自动闭合HTML标签
Vscode-icons 让 vscode 资源目录加上图标
Path Intellisense 自动路径补全、默认不带这个功能
Vue-color vue 语法高亮主题
4.6、常用插件配置
1、每次保存的时候自动格式化搜索 format On Save,勾选前面的复选框
2、每次保存的时候将代码按 eslint 格式进行修复搜索 Code Actions On Save,点击下方圈中的地方
"source.fixAll.eslint": true
3、在函数名和后面的括号之间加个空格
搜索 Insert Space Before Function Parenthesis,并将前面的复选框勾上
4、ESLint添加 vue 支持
搜索 validate,选择左边的 ESLint,点击后侧的圈中的地方
"eslint.validate": [ "html", "javascript", "javascriptreact", "vue" ]
5、用单引号替代双引号
搜索 Single Quote,勾选前面的复选框
6、代码结尾以分号结束
搜索 Semi,去掉前面的复选框
7、ESLint插件修改
解决方法就是:给.eslintrc.js中的rule添加: 'prettier/prettier': 'off',(注意:该项会让前面2个设置失效)
五、综合案例
5.1、在浏览器中直接使用Vue3.0
5.1.1、通过 CDN 使用 Vue
你可以借助 script 标签直接通过 CDN 来使用 Vue:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
这里我们使用了 unpkg,但你也可以使用任何提供 npm 包服务的 CDN,例如 jsdelivr 或 cdnjs。当然,你也可以下载此文件并自行提供服务。
通过 CDN 使用 Vue 时,不涉及“构建步骤”。这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成。但是,你将无法使用单文件组件 (SFC) 语法。
上面的例子使用了_全局构建版本_的 Vue,该版本的所有顶层 API 都以属性的形式暴露在了全局的 Vue
对象上。这里有一个使用全局构建版本的例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app">{{message}}</div> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script> const { createApp } = Vue; createApp({ data() { return { message: "Hello, Vue!", }; }, }).mount("#app"); </script> </body> </html>
运行结果:
5.1.2、通过 js文件使用 Vue
在浏览器端直接访问:https://unpkg.com/vue@3/dist/vue.global.js
新建一个vue3.js文件,将内容复制到文件中
在代码中引用vue3.js文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app">{{message}}</div> <script src="js/vue3.js"></script> <script> const { createApp } = Vue; createApp({ data() { return { message: "Hello, Vue!", }; }, }).mount("#app"); </script> </body> </html>
5.1.3、声明式渲染
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>vue3介绍</title> </head> <body> <div id="app1">{{message}}</div> <div id="app2"> <span v-bind:title="message"> 把鼠标放到这里试试 </span> </div> <script src="js/vue3.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const { createApp } = Vue; //vue应用对象 var app2 = createApp({ data() { return { message: "Hello Vue3!", }; }, }).mount("#app1"); //绑定属性 var app2 = createApp({ data() { return { message: "页面加载时间是:" + new Date().toLocaleString(), }; }, }).mount("#app2"); </script> </body> </html>
这里我们遇到了一点新东西。你看到的 v-bind
特性被称为指令。指令带有前缀 v-
,以表示它们是 Vue 提供的特殊特性。可能你已经猜到了,它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 title
特性和 Vue 实例的 message
属性保持一致”。
如果你再次打开浏览器的 JavaScript 控制台,输入 app2.message = '新消息'
,就会再一次看到这个绑定了 title
特性的 HTML 已经进行了更新。
我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 app.message
的值,你将看到上例相应地更新。
5.2、案例
5.2.1、条件与循环
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>vue3介绍</title> </head> <body> <div id="app3"> <span v-if="isShow"> isShow为true时你可以看到我 </span> </div> <div id="app4"> <span v-if="isShow"> <table border="1" cellspacing="1" cellpadding="1" width="50%"> <tr> <th>序号</th> <th>名称</th> <th>价格</th> </tr> <tr v-for="(obj,index) in fruits"> <td>{{index+1}}</td> <td>{{obj.name}}</td> <td>{{obj.price}}</td> </tr> </table> </span> </div> <script src="./js/vue3.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> //if指令 const { createApp } = Vue; //vue应用对象 var app3 = createApp({ data() { return { isShow: true, }; }, }).mount("#app3"); //循环指令 var app4 = createApp({ data() { return { isShow: true, fruits: [ { name: "苹果", price: "6.8", }, { name: "橙子", price: "3.5", }, { name: "香蕉", price: "2.3", }, ], }; }, }).mount("#app4"); </script> </body> </html>
这个例子演示了我们不仅可以把数据绑定到 DOM 文本或特性,还可以绑定到 DOM 结构。此外,Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用 过渡效果。
5.2.2、事件、处理用户输入与计算
为了让用户和你的应用进行交互,我们可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>vue3介绍</title> </head> <body> <div id="app5"> <button v-on:click="showMe">{{message}}</button> <input v-model="message" /> </div> <p id="app6"> <button v-on:click="n1+=1">+</button><input v-model="n1" />+ <input v-model="n2" />= <input v-model="sum" /> </p> <script src="./js/vue3.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> //事件 const { createApp } = Vue; var app5 = createApp({ data() { return { message: "vue 事件", }; }, methods: { showMe: function () { this.message = this.message.split("").reverse().join(""); }, }, }).mount("#app5"); //计算 var app6 = createApp({ data() { return { n1: 0, n2: 0, }; }, computed: { sum: function () { return parseInt(this.n1) + parseInt(this.n2); }, }, }).mount("#app6"); </script> </body> </html>
注意在 showMe方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。
Vue 还提供了 v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定。
六、天狗商城购物车案例
通过一个综合示例来快速了解Vue3,会使用到模板、计算,表达式、组件等等,要求实现一个简单的购物车,运行时的效果如下:
代码:
Cart.vue
<template> <h2>天狗商城购物车</h2> <table class="cartTable"> <tr> <th>序号</th> <th> 名称<img src="../assets/向上.png" width="20" height="20" @click="sheng1" class="img1"/> <img src="../assets/向下.png" width="20" height="20" @click="sheng2" class="img2"/> </th> <th>单价 <img src="../assets/向上.png" width="20" height="20" @click="sheng" class="img1"/> <img src="../assets/向下.png" width="20" height="20" @click="shengs" class="img2"/> </th> <th>数量 <img src="../assets/向上.png" width="20" height="20" class="img1" @click="up"/> <img src="../assets/向下.png" width="20" height="20" class="img2" @click="down"/> </th> <th>小计</th> <th>操作</th> </tr> <tr v-for="(product,index) in products" :key="product.title" :class="{bg:index%2==0}"> <td>{{index+1}}</td> <td>{{product.title}}</td> <td>{{formatPrice(product.price,2)}}</td> <td> <button @click="minus(product,index)">-</button> <input v-model="product.quantity" class="quantity" @keyup="product.quantity=(product.quantity>=0?product.quantity:1)"/> <button @click="add(product)">+</button> </td> <td>{{formatPrice(product.quantity*product.price,2)}}</td> <td> <button @click="remove(index)">删除</button> </td> </tr> <tfoot> <td colspan="6">{{formatPrice(total,2)}}</td> </tfoot> </table> </template> <script lang="ts"> import { computed, reactive } from 'vue'; type Product={ title:string, quantity:number, price:number } export default{ setup() { //产品数据,响应式对象 let products=reactive<Product[]>([ { title: 'paint pot', quantity: 9, price: 3.95 }, { title: 'polka dots', quantity: 17, price: 12.3 }, { title: 'pebbles', quantity: 5, price: 6.71 }, { title: 'Mi Note5', quantity: 8, price: 2985.6 }, { title: 'iPhone XS', quantity: 10, price: 8906.72 } ]); //计算属性,用于格式化价钱 const formatPrice=computed(()=>{ return function(value:number,n:number){ return "¥"+value.toFixed(n); } }); //计算属性,用于计算出总计 const total=computed(()=>{ let sum=0; products.forEach(p=>{ sum+=(p.price*p.quantity); }); return sum; }); //移除方法,用于删除购物车中的数据 const remove=function(index:number){ if(confirm("确定要删除吗?")){ //把数组products中从index的位置开始删除1条数据 products.splice(index,1); } } //减少数量方法 function minus(product:Product,index:number){ if(product.quantity>0){ product.quantity-=1; }else{ remove(index); } } //增加数量方法 function add(product:Product){ product.quantity+=1; } //按价格进行排序(升序) function sheng(){ products.sort((a,b)=>{ return a.price-b.price; }); } //按价格进行排序(降序) function shengs(){ products.sort((a,b)=>{ return b.price-a.price; }); } //按名称进行排序(升序) function sheng1(){ products.sort((a,b)=>{ if(a.title>b.title) return 1; if(a.title<b.title) return -1; return 0; }); } //按名称进行排序(降序) function sheng2(){ products.sort((a,b)=>{ if(a.title>b.title) return -1; if(a.title<b.title) return 1; return 0; }); } //按小计进行排序(升序) function up(){ products.sort((a,b)=>{ return a.quantity-b.quantity; }); } //按小计进行排序(降序) function down(){ products.sort((a,b)=>{ return b.quantity-a.quantity; }); } return{products,formatPrice,total,remove,minus,add,sheng,sheng1,sheng2,shengs,up,down}; } } </script> <style> .cartTable{ width: 100%; } .cartTable,.cartTable td,.cartTable th{ border-collapse: collapse; border: 1px solid #999; } .cartTable tfoot td{ text-align: right; padding: 10px; font-size: 18px; font-weight: bold; color: red; } .quantity{ width: 40px; } .bg{ background-color: #def; } .cartTable th{ position: relative; } .cartTable th img{ width:15px; height:15px; position: absolute; } .cartTable th .img1{ top:1px; cursor: pointer; } .cartTable th .img2{ bottom:-2px; cursor: pointer; } </style>
router/index.ts