Vue3+TypeScript从入门到进阶(一)——Vue3简介及介绍——附沿途学习案例及项目实战代码
一、简介二、Vue2和Vue3区别见后续文章三、Vue知识点学习见后续文章四、TypeScript知识点见后续文章五、项目实战见后续文章六、项目打包和自动化部署见后续文章七、沿途学习代码地址及案例地址1、沿途学习代码地址https://gitee.com/wu_yuxin/vue3-learning.git2、项目案例地址https://gitee.com/wu_yuxin/vue3-ts-cms
一、简介
1、认识Vue
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
-
全程是Vue.js或者Vuejs;
-
什么是渐进式框架呢?表示我们可以在项目中一点点来引入和使用Vue,而不一定需要全部使用Vue来开发整个项目;
渐进式: (自底向上逐层的应用)
(1) 简单应用: 只需要一个轻量小巧的核心库
(2) 复杂应用: 可以引用各种vue插件
这里说的渐进式框架其实是与Spring类似的分层架构,Vue由5个模块组成:声明式渲染/组件系统(vue.js)、客户端路由(vue-router)、大规模状态管理(vuex/pinia)、构建工具(vue-loader/webpack/vue-cli/vite)、数据持久化(涉及到后端,没有具体方案)
目前Vue在前端处于什么地位?
目前前端最流行的是三大框架:Vue、React、Angular。
2、前端框架数据对比
框架数据对比(Google指数)
框架数据对比(百度指数)
框架数据对比(npm下载量)
框架数据对比(GitHub)
谁是最好的前端框架?
当然,我不会去给出我的结论:
-
首先,这是一个敏感的话题,在很多地方都争论不休,就像很多人喜欢争论谁才是世界上最好的语言一样;
-
其次,争论这个话题是没有意义的,争论不休的话题;
但是,我们从现实的角度,分析一下,学习哪一门语言更容易找到工作?
-
找后端的工作:优先推荐Java、其次推荐Go、再次推荐Node(JavaScript),可能不推荐PHP、C#;
-
找前端的工作:优先推荐JavaScript(TypeScript)、其次Flutter、再次Android(Java、Kotlin)、iOS(OC、Swift);
-
也有很多的其他方向:游戏开发、人工智能、算法工程师等等;
那么,就前端来说,学习了HTML、CSS、JavaScript,哪一个框架更容易找到工作?
-
如果去国外找工作,优先推荐React、其次是Vue和Angular,不推荐jQuery了;
-
如果在国内找工作,优先推荐、必须学习Vue,其次是React,其次是Angular,不推荐jQuery了;
3、Vue3带来的变化
Vue对前端工程师的重要性
学习vue2还是vue3?
目前需要学习Vue3吗?
在2020年的9月19日,万众期待的Vue3终于发布了正式版,命名为**“One Piece”**。
-
它也带来了很多新的特性:更好的性能、更小的包体积、更好的TypeScript集成、更优秀的API设计。
-
在vue3刚刚发布时,很多人也是跃跃欲试,想要尝试vue3的各种新特性。
-
但是事实上在刚刚发布的时候我们使用vue3来写demo练习是没有问题的,真正在实际业务项目中使用vue3还需要一个相对的过程;
-
包括vue3的进一步稳定、包括社区更多vue3相关的插件、组件库的支持和完善。
那么现在是否是学习vue3的时间呢?
-
答案是肯定的
-
首先vue3在经过一系列的更新和维护后,已经是趋于稳定,并且在之前尤雨溪也宣布在今年(2021年)第二季度会将vue3作为Vue CLI的默认版本了。
-
目前社区也经过一定时间的沉淀,更加的完善了,包括AntDesignVue、Element-Plus都提供了对Vue3的支持,所以很多公司目前新的项目都已经在使用Vue3来进行开发了。
-
并且在面试的时候,几乎都会问到各种各样Vue3、Vite2工具相关的问题。
Vue3带来的变化(源码)
源码通过monorepo的形式来管理源代码:
-
Mono:单个
-
Repo:repository仓库
-
主要是将许多项目的代码存储在同一个repository中;
-
这样做的目的是多个包本身相互独立,可以有自己的功能逻辑、单元测试等,同时又在同一个仓库下方便管理;
-
而且模块划分的更加清晰,可维护性、可扩展性更强;
源码使用TypeScript来进行重写:
-
在Vue2.x的时候,Vue使用Flow来进行类型检测;
-
在Vue3.x的时候,Vue的源码全部使用TypeScript来进行重构,并且Vue本身对TypeScript支持也更好了;
Vue3带来的变化(性能)
使用Proxy进行数据劫持
-
在Vue2.x的时候,Vue2是使用Object.defineProperty来劫持数据的getter和setter方法的;
-
这种方式一致存在一个缺陷就是当给对象添加或者删除属性时,是无法劫持和监听的;
-
所以在Vue2.x的时候,不得不提供一些特殊的API,比如 s e t 或 set或 set或delete,事实上都是一些hack方法,也增加了开发者学习新的API的成本;
-
而在Vue3.x开始,Vue使用Proxy来实现数据的劫持,这个API的用法和相关的原理我也会在后续讲到;
删除了一些不必要的API:
-
移除了实例上的$on, $off 和 $once;
-
移除了一些特性:如filter、内联模板等;
包括编译方面的优化:
- 生成Block Tree、Slot编译优化、diff算法优化;
Vue3带来的变化(新的API)
由Options API 到 Composition API:
-
在Vue2.x的时候,我们会通过Options API来描述组件对象;
-
Options API包括data、props、methods、computed、生命周期等等这些选项;
-
存在比较大的问题是多个逻辑可能是在不同的地方:
比如created中会使用某一个method来修改data的数据,代码的内聚性非常差;
-
Composition API可以将 相关联的代码 放到同一处 进行处理,而不需要在多个Options之间寻找;
Hooks函数增加代码的复用性:
-
在Vue2.x的时候,我们通常通过mixins在多个组件之间共享逻辑;
-
但是有一个很大的缺陷就是 mixins也是由一大堆的Options组成的,并且多个mixins会存在命名冲突的问题;
-
在Vue3.x中,我们可以通过Hook函数,来将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的;
-
具体的好处,会在后续的课程中演练和讲解(包括原理);
4、系统学习Vue3+TypeScript
5、如何使用Vue呢?
Vue的本质,就是一个JavaScript的库:
-
刚开始我们不需要把它想象的非常复杂;
-
我们就把它理解成一个已经帮助我们封装好的库;
-
在项目中可以引入并且使用它即可。
那么安装和使用Vue这个JavaScript库有哪些方式呢?
-
方式一:在页面中通过CDN的方式来引入;
-
方式二:下载Vue的JavaScript文件,并且自己手动引入;
-
方式三:通过npm包管理工具安装使用它(webpack再讲);
-
方式四:直接通过Vue CLI创建项目,并且使用它;
6、引入方式一:CDN引入
什么是CDN呢?CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩 写:CDN)
-
它是指通过 相互连接的网络系统,利用最靠近每个用户的服务器;
-
更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
-
来提供高性能、可扩展性及低成本的网络内容传递给用户;
常用的CDN服务器可以大致分为两种:
-
自己的CDN服务器:需要购买自己的CDN服务器,目前阿里、腾讯、亚马逊、Google等都可以购买CDN服务器;
-
开源的CDN服务器:国际上使用比较多的是unpkg、 JSDelivr、cdnjs;
Vue的CDN引入:
<script src="https://unpkg.com/vue@next"></script>
Hello Vue案例的实现:
<div id="app">
<h2>Hello World</h2>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const why = {
template: '<h2>Hello World</h2>'
}
const app = Vue.createApp(why);
app.mount("#app")
</script>
7、引入方式二:下载和引入
下载Vue的源码,可以直接打开CDN的链接:
-
打开链接,复制其中所有的代码;
-
创建一个新的文件,比如vue.js,将代码复制到其中;
通过script标签,引入刚才的文件:
<script src="../js/vue.js"></script>
你好啊,Vue3,案例的实现:
<div id="app"></div>
<script src="../js/vue.js"></script>
<script>
Vue.createApp({
template: `<h2>你好啊, 李银河</h2>`
}).mount("#app");
</script>
8、声明式和命令式
原生开发和Vue开发的模式和特点,我们会发现是完全不同的,这里其实涉及到两种不同的编程范式:
-
命令式编程和声明式编程;
-
命令式编程关注的是 “how to do”,声明式编程关注的是 “what to do”,由框架(机器)完成 “how”的过程;
在原生的实现过程中,我们是如何操作的呢?
-
我们每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令;
-
这样的编写代码的过程,我们称之为命令式编程;
-
在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的;
在Vue的实现过程中,我们是如何操作的呢?
-
我们会在createApp传入的对象中声明需要的内容,模板template、数据data、方法methods;
-
这样的编写代码的过程,我们称之为是声明式编程;
-
目前Vue、React、Angular的编程模式,我们称之为声明式编程;
9、MVVM模型
MVC和MVVM都是一种软件的体系结构
-
MVC是Model – View –Controller的简称,是在前期被使用非常框架的架构模式,比如iOS、前端;
-
MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式;
通常情况下,我们也经常称Vue是一个MVVM的框架。
- Vue官方其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的。
① MVVM介绍
MVVM 是Model-View-ViewModel
的缩写,它是一种基于前端开发的架构模式,是一种事件驱动编程方式
Model
:vue对象的data属性里面的数据,这里的数据要显示到页面中View
:vue中数据要显示的HTML页面,在vue中,也称之为“视图模板” (HTML+CSS)ViewModel
:vue中编写代码时的vm对象,它是vue.js的核心,负责连接 View 和 Model数据的中转,保证视图和数据的一致性,所以前面代码中,data里面的数据被显示中p标签中就是vm对象自动完成的(双向数据绑定:JS中变量变了,HTML中数据也跟着改变)
② MVVM的特性
- 低耦合:
视图
(View)可以独立于Model变化和修改
,1个ViewModel可以绑定到不同的View上,当View变化的时候 Model可以不变,当Model变化的时候 View也可以不变 - 可复用:可以把一些视图逻辑放在1个ViewModel中,让很多View
重用这端视图的逻辑
(以此减少代码冗余) - 独立开发:
开发
人员可以专注于业务逻辑
和数据的开发
(ViewModel),设计
人员可以专注于页面设计
- 可测试:界面元素是比较难以测试的,而现在的测试可以
针对ViewModel
来编写
③ MVVM的逻辑
10、Vue的源码
如果想要学习Vue的源码,比如看createApp的实现过程,应该怎么办呢?
第一步:在GitHub上搜索 vue-next,下载源代码;
- 这里推荐通过 git clone 的方式下载;
第二步:安装Vue源码项目相关的依赖;
- 执行 yarn install
第三步:对项目执行打包操作
- 执行yarn dev(执行前修改脚本)
"scripts": {
"dev": "node scripts/dev.js --sourcemap"
}
第四步:通过 packages/vue/dist/vue.global.js 调试代码
二、Vue2和Vue3区别
Vue3+TypeScript从入门到进阶(二)——Vue2和Vue3的区别——附沿途学习案例及项目实战代码
三、Vue知识点学习
Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(四)——Vue3基础知识点(中)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(五)——Vue3基础知识点(下)——附沿途学习案例及项目实战代码
四、TypeScript知识点
Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码
五、项目实战
Vue3+TypeScript从入门到进阶(七)——项目实战——附沿途学习案例及项目实战代码
六、项目打包和自动化部署
Vue3+TypeScript从入门到进阶(八)——项目打包和自动化部署——附沿途学习案例及项目实战代码
七、沿途学习代码地址及案例地址
1、沿途学习代码地址
https://gitee.com/wu_yuxin/vue3-learning.git
2、项目案例地址
https://gitee.com/wu_yuxin/vue3-ts-cms.git
八、知识拓展
1、ES6数组与对象的解构赋值详解
数组的解构赋值
基本用法
ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构(Destructuring)
// 以前为变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
// ES6允许写成这样
var [a,b,c] = [1,2,3];
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
下面是一些使用嵌套数组进行解构的例子:
let [foo,[[bar],baz]] = [1,[[2],3]];
foo // 1
bar // 2
baz // 3
let [,,third] = ["foo","bar","baz"];
third // "baz"
let [head,...tail] = [1,2,3,4];
head // 1
tail // [2,3,4]
let [x,y,...z] = ['a'];
x // "a"
y // undefined
z // []
默认值
解构赋值允许制定默认值
var [foo = true] = [];
foo // true
[x,y='b'] = ['a'];
// x='a', y='b'
注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。
所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
var [x=1] = [undefined];
x //1
var [x=1] = [null];
x // null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值:
function f(){
console.log('aaa');
}
let [x=f()] = [1];
上面的代码中,因为x能取到值,所以函数f()根本不会执行。上面的代码其实等价于下面的代码:
let x;
if([1][0] === undefined){
x = f();
}else{
x = [1][0];
}
默认值可以引用解构赋值的其他变量,但该变量必须已经声明:
let [x=1,y=x] = [];
// x=1; y=1
let [x=1,y=x] = [2];
// x=2; y=2
let [x=1,y=x] = [1,2];
// x=1; y=2
let [x=y,y=1] = []; // ReferenceError
上面最后一个表达式,因为x用到默认值是y时,y还没有声明。
对象的解构赋值
1、最简单的案例
看下面的案例
let person = {
name: 'yhb',
age: 20
}
/*
注意:下面虽然看起来是创建了一个对象,对象中有两个属性 name 和 age
但是:其实是声明了两个变量
name:等于对象person 中的name属性的值
age:等于对象person 中的 age属性的值
*/
let { name, age } = person
console.log(name,age)
如上面注释中所说,声明了变量 name和age,然后分别从对象person中寻找与变量同名的属性,并将属性的值赋值给变量
所以,这里的关键,就是首先要知道对象中都有哪些属性,然后再使用字面量的方式声明与其同名的变量
2、属性不存在怎么办
如果不小心声明了一个对象中不存在的属性怎么办?
或者,实际情况下,可能是我们就是想再声明一个变量,但是这个变量也不需要从对象中获取值,这个时候,此变量的值就是 undefined
let person = {
name: 'yhb',
age: 20
}
let { name, age,address } = person
console.log(name,age,address)
此时,可以给变量加入一个默认值
let { name, age,address='北京' } = person
3、属性太受欢迎怎么办
当前声明了 name 和 age 变量,其值就是person对象中name和age属性的值,如果还有其他变量也想获取这两个属性的值怎么办?
let { name, age, address = '北京' } = person
console.log(name, age, address)
let { name, age } = person
console.log(name, age)
上面的方法肯定不行,会提示定义了重复的变量 name 和 age
那怎么办呢?
难道只能放弃结构赋值,使用老旧的方式吗?
let l_name=person.name
let l_age=person.age
console.log(l_name,l_age)
其实不然!
let {name:l_name,age:l_age}=person
console.log(l_name,l_age)
说明:
声明变量 l_name 并从对象person中获取name属性的值赋予此变量
声明变量 l_age, 并从对象person中获取age属性的值赋予此变量
这里的重点是下面这行代码
let {name:l_name,age:l_age}=person
按照创建对象字面量的逻辑,name 为键,l_name 为值。但注意,这里是声明变量,并不是创建对象字面量,所以争取的解读应该是
声明变量 l_name,并从person 对象中找到与 name 同名的属性,然后将此属性的值赋值给变量 l_name
所以,我们最后输出的是变量 l_name和l_age
console.log(l_name,l_age)
当然这种状态下,也是可以给变量赋予默认值的
let { name:l_name, age:l_age, address:l_address='北京' }=person
4、嵌套对象如何解构赋值
let person = {
name: 'yhb',
age: 20,
address: {
province: '河北省',
city: '保定'
}
}
// 从对象 person 中找到 address 属性,并将值赋给变量 address
let {address}=person
// 从对象 address 中找到 province 属性,并将值赋给变量 province
let {province}=address
console.log(province)
上面代码一层层的进行结构赋值,也可以简写为如下形式
let {address:{province}}=person
从peson 对象中找到 address 属性,取出其值赋值给冒号前面的变量 address,然后再将 变量address 的值赋值给 冒号 后面的变量 {province},相当于下面的写法
let {province}=address
字符串的解构赋值
1、字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
2、JavaScript的 …(展开运算符)
三个连续的点具有两个含义:展开运算符(spread operator)和剩余运算符(rest operator)。
展开运算符
展开运算符允许迭代器在接收器内部分别展开或扩展。迭代器和接收器可以是任何可以循环的对象,例如数组、对象、集合、映射等。你可以把一个容器的每个部分分别放入另一个容器。
const newArray = ['first', ...anotherArray];
剩余参数
剩余参数语法允许我们将无限数量的参数表示为数组。命名参数的位置可以在剩余参数之前。
const func = (first, second, ...rest) => {};
用例
定义是非常有用的,但是很难仅从定义中理解概念。我认为用日常用例会加强对定义的理解。
复制数组
当我们需要修改一个数组,但又不想改变原始数组(其他人可能会使用它)时,就必须复制它。
const fruits = ['apple', 'orange', 'banana'];
const fruitsCopied = [...fruits]; // ['apple', 'orange', 'banana']
console.log(fruits === fruitsCopied); // false
// 老方法
fruits.map(fruit => fruit);
它正在选择数组中的每个元素,并将每个元素放在新的数组结构中。我们也可以使用 map 操作符实现数组的复制并进行身份映射。
唯一数组
如果我们想从数组中筛选出重复的元素,那么最简单的解决方案是什么?
Set 对象仅存储唯一的元素,并且可以用数组填充。它也是可迭代的,因此我们可以将其展开到新的数组中,并且得到的数组中的值是唯一的。
const fruits = ['apple', 'orange', 'banana', 'banana'];
const uniqueFruits = [...new Set(fruits)]; // ['apple', 'orange', 'banana']
// old way
fruits.filter((fruit, index, arr) => arr.indexOf(fruit) === index);
串联数组
可以用 concat 方法连接两个独立的数组,但是为什么不再次使用展开运算符呢?
const fruits = ['apple', 'orange', 'banana'];
const vegetables = ['carrot'];
const fruitsAndVegetables = [...fruits, ...vegetables]; // ['apple', 'orange', 'banana', 'carrot']
const fruitsAndVegetables = ['carrot', ...fruits]; // ['carrot', 'apple', 'orange', 'banana']
// 老方法
const fruitsAndVegetables = fruits.concat(vegetables);
fruits.unshift('carrot');
将参数作为数组进行传递
当传递参数时,展开运算符能够使我们的代码更具可读性。在 ES6 之前,我们必须将该函数应用于 arguments。现在我们可以将参数展开到函数中,从而使代码更简洁。
const mixer = (x, y, z) => console.log(x, y, z);
const fruits = ['apple', 'orange', 'banana'];
mixer(...fruits); // 'apple', 'orange', 'banana'
// 老方法
mixer.apply(null, fruits);
数组切片
使用 slice 方法切片更加直接,但是如果需要的话,展开运算符也可以做到。但是必须一个个地去命名其余的元素,所以从大数组中进行切片的话,这不是个好方法。
const fruits = ['apple', 'orange', 'banana'];
const [apple, ...remainingFruits] = fruits; // ['orange', 'banana']
// 老方法
const remainingFruits = fruits.slice(1);
将参数转换为数组
Javascript 中的参数是类似数组的对象。你可以用索引来访问它,但是不能调用像 map、filter 这样的数组方法。参数是一个可迭代的对象,那么我们做些什么呢?在它们前面放三个点,然后作为数组去访问!
const mixer = (...args) => console.log(args);
mixer('apple'); // ['apple']
将 NodeList 转换为数组
参数就像从 querySelectorAll 函数返回的 NodeList 一样。它们的行为也有点像数组,只是没有对应的方法。
[...document.querySelectorAll('div')];
// 老方法
Array.prototype.slice.call(document.querySelectorAll('div'));
复制对象
最后,我们介绍对象操作。复制的工作方式与数组相同。在以前它可以通过 Object.assign 和一个空的对象常量来实现。
const todo = { name: 'Clean the dishes' };
const todoCopied = { ...todo }; // { name: 'Clean the dishes' }
console.log(todo === todoCopied); // false
// 老方法
Object.assign({}, todo);
合并对象
合并的唯一区别是具有相同键的属性将被覆盖。最右边的属性具有最高优先级。
const todo = { name: 'Clean the dishes' };
const state = { completed: false };
const nextTodo = { name: 'Ironing' };
const merged = { ...todo, ...state, ...nextTodo }; // { name: 'Ironing', completed: false }
// 老方法
Object.assign({}, todo, state, nextTodo);
需要注意的是,合并仅在层次结构的第一级上创建副本。层次结构中的更深层次将是相同的引用。
将字符串拆分为字符
最后是字符串。你可以用展开运算符把字符串拆分为字符。当然,如果你用空字符串调用 split 方法也是一样的。
const country = 'USA';
console.log([...country]); // ['U', 'S', 'A']
// 老方法
country.split('');
3、export ‘defineEmit’ (imported as ‘defineEmit’) was not found in ‘vue’
在学习vue3的顶层编写方式时的父子组件通信的时候,我们会看到一些比较老(2020、2021年初)的博客里面会有使用defineEmit的,但是如果我们用比较新版本的Vue3的话,就会报错。原因是,新版本的Vue3将defineEmit改成了defineEmits了
九、其他知识学习
1、Webpack学习
2、数据可视化-echarts
数据可视化-echarts入门、常见图表案例、超详细配置解析及项目案例
3、预备知识与后续知识及项目案例
HTML入门与进阶以及HTML5
CSS
JS-上
JS-下
jQuery
Node.js + Gulp 知识点汇总
MongoDB + Express 入门及案例代码
4、Vue2学习
5、JavaScript面向对象和设计模式
6、微前端学习
更多推荐
所有评论(0)