一、简介
Vue3+TypeScript从入门到进阶(一)——Vue3简介及介绍——附沿途学习案例及项目实战代码
二、Vue2和Vue3区别
Vue3+TypeScript从入门到进阶(二)——Vue2和Vue3的区别——附沿途学习案例及项目实战代码
三、Vue知识点学习
Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(四)——Vue3基础知识点(中)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(五)——Vue3基础知识点(下)——附沿途学习案例及项目实战代码
四、TypeScript知识点
Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码
五、项目实战
Vue3+TypeScript从入门到进阶(七)——项目实战——附沿途学习案例及项目实战代码
六、项目打包和自动化部署
一. 项目部署和DevOps
1.1. 传统的开发模式
在传统的开发模式中,开发的整个过程是按部就班就行:
但是这种模式存在很大的弊端:
- 工作的不协调:开发人员在开发阶段,测试和运维人员其实是处于等待的状态。等到测试阶段,开发人员等待测试反馈bug,也会处于等待状态。
- 线上bug的隐患:项目准备交付时,突然出现了bug,所有人员需要加班、等待问题的处理;
1.2. DevOps开发模式
DevOps是Development和Operations两个词的结合,将开发和运维结合起来的模式:
1.3. 持续集成和持续交付
伴随着DevOps一起出现的两个词就是持续集成和持续交付(部署):
- CI是Continuous Integration(持续集成);
- CD是两种翻译:Continuous Delivery(持续交付)或Continuous Deployment(持续部署);
持续集成CI:
持续交付和持续部署:
1.4. 自动化部署流程
二. 购买云服务器
2.1. 注册阿里云的账号
云服务器我们可以有很多的选择:阿里云、腾讯云、华为云。
- 目前在公司使用比较多的是阿里云;
- 我自己之前也一直使用阿里云,也在使用腾讯云;
- 之前华为云也有找我帮忙推广他们的活动;
但是在我们的课程中,我选择目前使用更加广泛的阿里云来讲解:
我们需要注册阿里云账号
-
https://aliyun.com/
-
注册即可,非常简单
2.2. 购买云服务器
购买云服务器其实是购买一个实例。
1.来到控制台:
2.创建实例,选择类型和配置
3.配置网络安全组
4.创建实例
三. 搭建服务器环境
3.1.CentOS安装和使用DNF
DNF新一代的RPM软件包管理器。他首先出现在 Fedora 18 这个发行版中。而最近,他取代了YUM,正式成为 Fedora 22 的包管理器。
DNF包管理器克服了YUM包管理器的一些瓶颈,提升了包括用户体验,内存占用,依赖分析,运行速度等多方面的内容。
DNF使用 RPM, libsolv 和 hawkey 库进行包管理操作。尽管它没有预装在 CentOS 和 RHEL 7 中,但你可以在使用 YUM 的同时使用 DNF 。
DNF 的最新稳定发行版版本号是 1.0,发行日期是2015年5月11日。 这一版本的额 DNF 包管理器(包括在他之前的所有版本) 都大部分采用 Pathon 编写,发行许可为GPL v2.
– 安装 DNF 包管理器
DNF 并未默认安装在 RHEL 或 CentOS 7系统中,但是 Fedora 22 已经默认使用 DNF .
1.为了安装 DNF ,您必须先安装并启用 epel-release 依赖。
在系统中执行以下命令:
yum install epel-release
或者
yum install epel-release -y
其实这里并没有强制使用”-y”的理由,相反的,在不使用”-y”的情况下,用户可以在安装过程中查看到底有哪些东西被安装进了系统。但对于没有这个需求的用户,您可以在 YUM 中使用”-y”参数来自动安装所有东西。
2.使用 epel-release 依赖中的 YUM 命令来安装 DNF 包。、
在系统中执行以下命令:
yum install dnf
然后, DNF 包管理器就被成功的安装到你的系统中了。接下来,是时候开始我们的教程了!在这个教程中,您将会学到27个用于 DNF 包管理器的命令。使用这些命令,你可以方便有效的管理您系统中的 RPM 软件包。现在,让我们开始学习 DNF 包管理器的27条常用命令吧!
– 查看 DNF 包管理器版本
用处:该命令用于查看安装在您系统中的 DNF 包管理器的版本
命令:
dnf –version
3.2. jenkins自动化部署
3.2.1. 安装Java环境
Jenkins本身是依赖Java的,所以我们需要先安装Java环境:
- 这里我安装了Java1.8的环境
dnf search java-1.8
dnf install java-1.8.0-openjdk.x86_64
3.2.2. 安装Jenkins
因为Jenkins本身是没有在dnf的软件仓库包中的,所以我们需要连接Jenkins仓库:
- wget是Linux中下载文件的一个工具,-O表示输出到某个文件夹并且命名为什么文件;
- rpm:全称为The RPM Package Manage,是Linux下一个软件包管理器;
wget –O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
# 导入GPG密钥以确保您的软件合法
rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
# 或者
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
编辑一下文件/etc/yum.repos.d/jenkins.repo
- 可以通过vim编辑
[jenkins]
name=Jenkins-stable
baseurl=http://pkg.jenkins.io/redhat
gpgcheck=1
安装Jenkins
dnf install jenkins # --nogpgcheck(可以不加)
启动Jenkins的服务:
systemctl start jenkins
systemctl status jenkins
systemctl enable jenkins
Jenkins默认使用8080端口提供服务,所以需要加入到安全组中:
3.2.3. Jenkins用户
我们后面会访问centos中的某些文件夹,默认Jenkins使用的用户是 jenkins
,可能会没有访问权限,所以我们需要修改一下它的用户:
修改文件的路径:/etc/sysconfig/jenkins
之后需要重启一下Jenkins:
systemctl restart jenkins
3.2.4. Jenkins配置
打开浏览器,输入:http://8.134.60.235:8080/
- 注意:你输入自己的IP地址
获取输入管理员密码:
- 在下面的地址中
cat /var/lib/jenkins/secrets/initialAdminPassword
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGFIYzwF-1653820193033)(/Users/coderwhy/Library/Application Support/typora-user-images/image-20201203173047824.png)]
可以安装推荐的插件:
3.2.5. Jenkins任务
新建任务:
配置项目和保留策略:
源码管理:
构建触发器:
这里的触发器规则是这样的:
- 定时字符串从左往右分别是:分 时 日 月 周
#每半小时构建一次OR每半小时检查一次远程代码分支,有更新则构建
H/30 * * * *
#每两小时构建一次OR每两小时检查一次远程代码分支,有更新则构建
H H/2 * * *
#每天凌晨两点定时构建
H 2 * * *
#每月15号执行构建
H H 15 * *
#工作日,上午9点整执行
H 9 * * 1-5
#每周1,3,5,从8:30开始,截止19:30,每4小时30分构建一次
H/30 8-20/4 * * 1,3,5
构建环境:
注意:我们需要搭建Node的环境
- 第一步:配置Node的环境;
- 第二步:安装Node的插件;
第一步:配置Node的环境
第二步:安装Node的插件
- 这里因为我已经安装过了,所以没有搜索到;
构建执行的任务:
- 查看Node的版本等是否有问题;
- 执行
npm install
安装项目的依赖; - 移除原来mall_cms文件的所有内容;
- 将打包的dist文件夹内容移动到mall_cms文件夹;
pwd
node -v
npm -v
npm install
npm run build
pwd
echo '构建成功'
ls
# 删除/root/mall_cms文件夹里所有的内容
rm -rf /root/mall_cms/*
cp -rf ./dist/* /root/mall_cms/
3.2.6 Jenkins执行任务时 permission denied
Jenkins执行任务shell时,有时需要进行删除文件或者移动并删除文件等操作。但是Jenkins的权限组时 Jenkins。
但是我们创建的文件有时候的权限会这个高。比如文件夹的权限是root的。这个时候执行脚本就会报错
但是这个网上找了很多方式,比如把Jenkins的权限组换成root,但是这样会出现新的问题,因为Jenkins的启动会用到JDK,如果把Jenkins的权限组换成了root,那JDK就无法辅助Jenkins启动了。
所以最简单的方式是 将 Jenkins要进行操作的文件夹的上层文件夹的权限进行更换,换成Jenkins的
chown -R jenkins:jenkins 目标文件夹路径
3.3. nginx安装和配置
3.3.1. 安装nginx
后续我们部署会使用nginx,所以需要先安装一下nginx:
dnf install nginx
启动nginx:
systemctl start nginx
systemctl status nginx
systemctl enable nginx
3.3.2. 配置nginx
我们这里主要配置nginx的用户和默认访问目录:
配置用户:
通过Linux命令创建文件夹和文件:
mkdir /root/mall_cms
cd /root/mall_cms
touch index.html
vi index.html
配置访问目录:
七、沿途学习代码地址及案例地址
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入门、常见图表案例、超详细配置解析及项目案例