笔者最近在学习李一鸣老师著的《Vue.js3+TypeScript从入门到项目实践》,01已经完结。
目录
1.初识 Vue.js
1.1 Vue版本的区别
Vue 1.x 是第一个发布的Vue版本。它的核心思想是“双向数据绑定”,这意味着当数据发生变化时,视图会自动更新。Vue 1.x 的API相对简单,并且性能较好,但也存在一些问题缺点,如性能受限于实现方式、缺乏虚拟DOM等。
Vue 2.x 是Vue的第二个版本,他在1的基础上进行了重构和优化。2代的核心优化是引入了虚拟DOM。这样就能高效地进行视图更新。此外2代还加入了许多新特性和语法糖,如计算属性、指令修饰和渲染函数等,使其更加灵活和易用。
Vue 3.x 是Vue的最新版本,它在2代的基础上进行了优化。主要优化了性能和开发体验,首先3代使用了新的响应式系统,性能比2代好很多,并且易于调试;其次3代采用了模块化架构,可以更好地支持Tree Shaking功能和按需加载;此外,还提供了Composition API,这是一种新的API风格,可以更好地组织和复用代码。
1.2 从零开始搭建Vue开发环境
1.2.1安装node.js 和 NPM
Node.js是基于Chrome V8 引擎的JavaScript运行时环境,而NPM(Node package Manager,Node包管理器)是Node.js默认的以JavaScript编写的软件包管理系统。
Vue的官方文档明确指出,使用Vue的前提是安装15.0或更高版本的Node.js。安装方法十分简单,打开Node.js的官网https://nodejs.org/zh-cn,选择对应自己操作系统版本的安装包,安装完成之后,NPM会随之自动安装到系统中,在命令行中输入npm -v 和 node -v 可以查看是否安装成功。
1.2.2 安装Git
Git 是一个开源的分布式版本控制系统,目前几乎所有开源项目都发布在使用Git的GitHub网站上,包括Vue这个开源项目也上传在该平台。
使用Vue CLI之前,需要安装好Git ,这样在使用Vue CLI创建项目时,将会自动调用Git命令,从GitHub把对应版本号的Vue模板与支持文件下载到本地。
Git 的安装,可以去官网下载,https://git-scm.com/downloads,安装完成后输入指令
git --version 可以查看安装的版本。
1.2.3 安装Vue CLI
接下来需要使用刚刚安装的NPM进行Vue CLI的安装,注意要在管理员权限下安装。Vue CLI是Vue的命令行界面工具,具有创建项目、添加文件以及启动服务等功能。使用以下命令安装Vue CLI:
npm install -g @vue/cli
安装过程可能会出一些问题,因为npm源(即仓库,registry)默认是境外服务器,在国内访问较慢,因此,在初次使用时,应该将npm源更换为国内的服务器地址,例如使用 https://registry.npmmirror.com作为npm源。代码如下:
npm config set registry https://registry.npmmirror.com
配置成功后,可通过 get 命令可查看npm源:
npm config get registry
现在回过头安装Vue CLI就没什么问题了,可以查看安装的Vue CLI的版本:
vue -V
笔者在实际操作中还遇到了一个问题,就是无法查到Vue CLI的版本,哪怕已经安装成功了。我通过设置环境变量的方式解决了这个问题。
首先在命令行输入 npm config get prefix
,记下输出的路径(如 C:\Users\你的用户名\AppData\Roaming\npm
);之后打开环境变量,找到Path这一行,点击编辑,再点击新建,将刚刚记录下的路劲输入,之后确定退出即可,重启命令提示符窗口,再次输入vue -V,就可以成功查询到Vue CLI的版本。
1.2.4 安装 Visual Studio Code
VS Code 是微软开发的一个轻量且强大的代码编辑器,不仅免费开源,而且提供相当丰富的插件。Vue所使用的编程语言TypeScript也是由微软开发的,这里推荐使用VSCode进行开发,当然如果读者已经习惯了其他开发软件,这一节可以跳过。
安装VSCode可以去官网:https://code.visualstudio.com/。
1.3 第一个Vue程序
创建Vue项目非常简单,打开终端,使用cd命令进入想要创建项目的目录下,执行创建命令即可。
vue create 你的vue项目名称
选择Vue 3 的版本,等待程序执行完成,接下来执行两句代码就可以启动Vue开发服务器:
cd hello-vue
npm run serve
1.3.1 Vue 的目录结构
创建完项目后,可以使用VSCode打开,目录如下:
- node_modules : 执行 npm install 后生成的文件夹,package.json中的第三方模块将会全部安装在其中;
- public : 公共资源目录,该文件夹得资源不会被Webpack处理,需要用绝对路径来引用;
- src: 主要代码的存放目录,后面的工作大多在这个目录下完成;
- gitignore :Git 配置文件,会在提交时忽略不需要的文件;
- babel.config.js : babel 的配置文件,作用于整个项目;
- jsconfig.json : JavaScript配置文件,可以用来配置默认的根路径等;
- package-lock.json : NPM配置文件,记录项目依赖包的精确版本及依赖关系树,确保团队成员或 CI 环境安装的依赖版本完全一致,避免版本不一致导致的问题;
- package.json : 项目的元数据文件,记录项目名称、版本、描述、依赖(
dependencies
)、开发依赖(devDependencies
)以及可执行脚本(scripts
)等信息,通过npm
或yarn
管理项目依赖和运行脚本; - README.md:项目说明文档,使用markdown编写;
- vue.config.js:项目配置文件,Webpack等大多数配置都在这里进行。
src文件下存放的是主要代码,下面分析其中文件的作用:
- assets:主要存放图片等资源;
- components:用于存放组件;
- App.vue:根组件,之后创建的页面都在这个节点之下;
- main.js:程序入口,createApp方法在本文件中执行。
上图中可以看出,src中的文件并不多,因为这个指令生成的是最基本的项目,并没有router和TypeScript等配置。
1.3.2 使用Vue CLI创建项目
Vue CLI的全名是Vue 命令行界面,它是一个帮助开发者快速构建Vue集成相关工具链的工具。它可以确保各种构建工具能够基于智能的默认配置平稳链接,这样开发者可以专注在撰写应用上,而不必纠结配置的问题。
首先来熟悉下命令行中的各种指令,输入 “vue help”可以查看:
- vue create:新建项目,前面创建的hello-vue就是用这个命令创建的;
- vue add:安装插件,相当于 npm install 和 vue invoke 命令都执行了;
- vue invoke :vue add的子功能,用户更改插件的配置;
- vue inspect :通过vue-cli-service 导出Webpack的配置到项目目录中;
- vue serve :相当于 npm run serve 命令;
- vue build :相当于 npm run build 命令;
- vue ui :可视化的图形界面,用于创建、更新和管理Vue项目;
- vue init:初始化项目,与 vue create类似,需要详细选择不同的配置项;
- vue config:检查和修改配置;
- vue outdated:检查服务或者插件是否过期;
- vue upgrade:升级cli-service 和 plugins;
- vue migrate :迁移已安装的插件;
- vue info:打印当前环境的调试信息;
- vue help:输出帮助信息。
vue.config.js 是一个可选的配置文件,如果项目的(和package.json同级的)根目录中存在这个文件,那么它会被@vue/cli-service 自动加载。当然,也可以使用package.json中的vue字段,但是注意这种写法需要严格遵守 JSON 格式。接下来介绍 vue.config.js 的常用配置,初次接触会觉得很乱,vue.config.js的配置是开发过程中很重要的一步,这些功能在后面的项目会经常使用,多尝试、多使用自然就会记住的。Vue CLI在未来可能会有改动,可以参考其最新的官方文档,网址为http:// https://cli.vuejs.org/zh/config/。
(1)基础配置
publicPath
-
作用:部署应用时的基本 URL(影响静态资源路径)。
-
示例:
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/'
};
outputDir
-
作用:构建输出的目录(默认
dist
)。 -
示例:
module.exports = {
outputDir: 'build'
};
assetsDir
-
作用:静态资源目录(相对于
outputDir
)。 -
示例:
module.exports = {
assetsDir: 'static'
};
(2)开发服务器配置
devServer
-
作用:配置开发服务器行为(如代理、端口等)。
-
常用子项:
-
port
: 开发服务器端口。 -
proxy
: 配置 API 代理,解决跨域问题。 -
open
: 启动后自动打开浏览器。
-
-
示例:
module.exports = {
devServer: {
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
},
open: true
}
};
(3)Webpack 配置
chainWebpack
-
作用:通过链式操作修改 Webpack 配置(推荐)。
-
示例:添加自定义 Loader 或插件。
module.exports = {
chainWebpack: (config) => {
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/icons'))
.end();
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(path.resolve(__dirname, 'src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader');
}
};
configureWebpack
-
作用:直接合并 Webpack 配置(对象或函数形式)。
-
示例:
module.exports = {
configureWebpack: {
plugins: [new MyCustomPlugin()]
}
};
(4)CSS 相关配置
css.loaderOptions
-
作用:向 CSS 预处理器传递全局选项。
-
示例:全局引入 SCSS 变量:
module.exports = {
css: {
loaderOptions: {
scss: {
additionalData: `@import "~@/styles/variables.scss";`
}
}
}
};
(5)生产环境优化
productionSourceMap
-
作用:是否生成生产环境的 Source Map(默认
true
,建议关闭以加速构建)。 -
示例:
module.exports = {
productionSourceMap: false
};
(6)其他常用配置
lintOnSave
-
作用:是否在保存时进行代码检查(ESLint)。
-
示例:
module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production'
};
transpileDependencies
-
作用:显式转译指定的依赖(兼容旧浏览器)。
-
示例:
module.exports = {
transpileDependencies: ['vue-echarts', 'swiper']
};
filenameHashing
-
作用:是否生成带有哈希的文件名(默认
true
)。 -
示例:
module.exports = {
filenameHashing: false
};
integrity
-
作用:启用 Subresource Integrity (SRI)。
-
示例:
module.exports = {
integrity: true
};
1.3.3 使用 Vite 创建项目
Vite 是一个快速的Web开发构建工具,由Vue团队维护,它提供了一种快速的开发体验。Vite提供了快速的开发服务器和即时热更新,支持 Vue,React 和 Svelte 等前端框架,它使用了原生ES模块来加载代码,可以显著提升构建速度。执行以下命令安装Vite:
// 全局安装Vite 的命令
npm install vite -g
//安装完成后查看版本号
vite -v
安装完成后,使用create-vue 创建Vue 项目。直接使用NPM命令即可创建Vite项目:
npm create vue@3
Vite的优势在于其可以极大地提高构建速度,在初学阶段使用Vue CLI 完全没有问题,但是未来地项目开发可能会逐渐转为Vite,因此建议使用Vite创建。
1.3.4 使用CDN创建项目
新建一个文件cdn.html,用于展示不使用构建工具,使用CDN来加载Vue。用VSCode打开cdn.html并输入 html:5,会自动生成一段HTML5的空白模板代码。
接下来在head 标签中引入CDN,并在body标签中输入以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue 3 的脚本 -->
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">{{message}}</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
message:'hello Vue!',
}
}
}).mount('#app')
</script>
</html>
打开网页,会发现页面已经发生了变化:
1.3.5 如何高效学习 vue
首先需要了解Vue地基础概念和语法,包括Vue地核心概念、组件化思想及指令等基础内容。这些内容可以通过阅读Vue3的官方文档和相关教程来学习。
其次,完整掌握Vue的生命周期、组件和指令等进阶内容。通过学习官方文档或是别人的代码来不断积累编程技巧和实践经验。建议使用Vue3搭建一些小型的项目。
最后,深入阅读Vue的源码,从底层了解其实现原理,这样可以更加深入地理解Vue,这样可以提高代码调试能力和排错能力。另外,建议了解一些周边地生态工具,如Vuex和Vue Router等,这些工具可以帮助开发者更好地应用Vue。
总之,Vue上手容易,精通困难,熟能生巧。
1.4 探索 Vue UI 库
一个优秀的框架不仅提供了吸引用户的页面风格,还可以显著提高开发效率,而不必在UI和CSS方面花费过多精力。本节介绍一些常见的UI框架。
1.4.1 Elemen-Plus 库
官网:https://element-plus.org/zh-CN/
1.4.2 Ant Design Vue 快速开发
官网:https://www.antdv.com/docs/vue/introduce-cn
1.4.3 打造轻巧的应用:Vant3 组件库
官网:https://develop365.gitlab.io/vant/v3/zh-CN/home/
1.4.4 跨平台开发利器:uni-app 框架
官网:https://uniapp.dcloud.net.cn/quickstart.html
笔者认为:实践比概念重要,后续章节会通过大量的案例和实例代码来演示。
2.TypeScript 基础知识
TypeScript 是 JavaScript 的一种超集,兼容JavaScript 的所有语法,并提供了额外的静态类型系统、类和接口等特性。这些额外的特性使得TypeScript的代码变得更加易读和易维护。
本章主要内容如下:
- TypeSCript 简介;
- 基础数据类型;
- 函数;
- 类;
- 泛型;
- 交叉类型与联合类型。
2.1 TypeScript 简介
TypeScript 作为JavaScript的超集,添加了可选的静态类型和基于类的面向对象编程。对于有JavaScript基础的读者来说,入门TypeScript很简单,只要掌握基础类型就可轻松上手,但是当应用越来越复杂时,容易把TypeScript写成AnyScript(即大量地把变量设置为any类型)。因此,想要完全掌握TypeScript的特性,还需系统的学习。
2.1.1 动态语言与静态语言
学习TypeScript之前,先了解下动态语言类型和静态语言类型。
(1)动态语言类型
动态语言在运行期才进行类型检查。其主要优点在于可以少写很多类型声明代码,更自由并且易于阅读。JavaScript 就是一门动态类型语言。
(2)静态语言类型
静态语言在编译期就会进行数据类型检查,优点在于类型的强制声明,使得IDE有很强的代码感知能力,能在早期发现一些问题,方便调试。TypeScript 就是一门静态类型语言。
2.1.2 搭建开发环境
(1)通过在线网址运行
官网:https://ts.nodejs.cn/play/
打开网址就能使用,还支持切换TypeScript版本,不想折腾的话直接打开网址就可以进行下一节的练习。
(2) 使用ts-node在本地运行
这里还是建议本地运行,在本地创建ts文件,通过VSCode打开进行编写。
如果想要搭建本地运行环境,输入以下代码使用NPM进行安装:
// 安装typescript
npm install -g typescript
// 安装typescript的Node.js运行环境
npm install -g ts-node
// 查看typescript的版本
tsc --version
// 查看node.js 的版本
ts-node --version
使用方法很简单,首先新建一个test.ts 文件并在其中输入一行测试代码:
console.log('hello typescript')
之后使用命令行进入test.ts文件中并使用ts-node运行该文件:
ts-node test.ts
2.2 基础数据类型
2.2.1 布尔类型
// 声明一个布尔类型变量
let bool : boolean = false;
console.log(bool);
2.2.2 数字类型
TypeScript 中都是浮点数,整数可直接与小数做运算。
let num1:number = 10;
let num2:number = 2.41;
console.log(num1+num2); //12.41
2.2.3 字符串类型
TypeScript 中的字符串使用单引号或双引号来表示,并且也支持ES6 中的反引号“ ` ”来操作。
let str1:string = 'hello';
let str2:string = 'typescript';
let str3:string = `${str1} ${str2}`;
console.log(str3); //hello typescript
2.2.4 数组类型与元组类型
在TypeScript中定义数组的方式有两种,元组类型实际上也就是数组类型,只是允许数组添加不同类型的值。
let arr1:number[];
arr1 = [1,2,3]; //正确赋值
arr1 = ['a','2','c']; //错误赋值
//使用数组泛型,Array<元素类型>
let arr2:Array<number> = [1,2,3];
//定义元组类型
let arr3 :[string,number];
arr3 = ['zhangsan',10]; //正确赋值
arr3 = [12,'zhangsan']; //错误赋值
// 拓展
type Person = [string,number];
let arr4 :Person[] = [
['zhangsan',21],
['lisi',22],
]
2.2.5 枚举类型
为防止代码出现过多的魔力数字(通常指缺乏解释或命名的独特数值),有必要使用枚举类型,默认从0开始编号,也可以手动指定成员数值。
// 指定第一个
enum Color {red=1,green,blue};
console.log('red=',Color.red); //1
console.log('green=',Color.green); //2
console.log('blue=',Color.blue); //3
// 全部指定
enum Animal {dog=2,cat=5,bird=3};
console.log(Animal);
// 通过数值获取定义名
console.log('2=',Animal[2]);
console.log('5=',Animal[5]);
console.log('3=',Animal[3]);
2.2.6 any 类型
any类型相当于移除类型检查,它允许像JavaScript一样任意赋值。
// 声明一个any类型变量
let age:any =18;
age = '12qrr';
console.log(age);
2.2.7 void 类型
void 类型就是没有类型,赋值只能是 undefined 或 null,一般用处不大。
// undefined
let un :void = undefined;
// 返回值为空
function fn():void{};
// 通常省略
function fn1(){}
2.2.8 null 和 undefined 类型
let A:undefined = undefined;
let B:null = null;
2.2.9 never 类型
never 表示不可能存在的值的类型。通常用于那些会导致错误或抛出异常而永远不会正常返回的函数的返回值类型。
如,当函数内部抛出异常或包含无限循环时,函数将永远不会返回正常结果,这时可以标注为never。
// 函数内部抛出异常,返回类型标注为never
function fn(message:string):never{
throw new Error(message)
}
// 无限循环,返回类型标注为never
function fn1():never{
while(1){
console.log(1);
}
}
2.3 函数
2.3.1 函数的使用
TypeScript 中的函数在使用时,必须声明函数的参数类型和返回类型。
function add(a:number,b:number){
return a+b;
}
function merge(a:string,b:string){
return a+b;
}
2.3.2 构造函数
构造函数主要用于创建对象时初始化对象,常与new一起使用。TypeScript 的构造函数用关键字constructor 来实现,可以使用this 来访问当前类的属性和方法。
class Student{
name:string;
age:number;
constructor(name:string,age:number){
this.name = name;
this.age =age;
}
}
let stu1 = new Student('zhangsan',19);
console.log(stu1);
2.3.3 可选参数
可选参数就是指不一定非要给函数传入这个参数,只需要在参数后面增加问好标识即可实现。
function add(a:number,b?:number){
if(b){
return a+b;
}else{
return a;
}
}
2.3.4 默认参数
只需要在参数后用等号进行赋值即可。
function add(a:number,b:number=5){
return a+b;
}
console.log(add(5,6)); // 11
console.log(add(1)); // 1
2.3.5 箭头函数
在JavaScript中,this 的指向是一个常见的问题,先看一个例子:
const Person ={
'name':'zhangsan',
'PrintName':function(){
let fun = function(){
return this.name;
};
return fun();
}
}
console.log(Person.PrintName()); // undefined
当我们通过“Person.PrintName()”调用函数时,PrintName()函数内部的this指向Person,但是内部函数fun是直接以fun()形式调用的普通函数,普通函数在非严格模式下,其this默认指向全局对象,严格模式下,this会是undefined。
要解决这个问题,通常先声明一个self变量,在函数外部正确绑定this,在函数内部通过self调用name属性,因为在对象方法中,this 的值代表当前的对象。
const Person ={
'name':'zhangsan',
'PrintName':function(){
let self = this;
let fun = function(){
return self.name;
}
return fun();
}
}
console.log(Person.PrintName()); // zhangsan
箭头函数提供了另一种方便的解决方法,它的内部的this是词法作用域,简单来说,箭头函数定义在哪里,它的this就指向谁。
const Person = {
'name': 'zhangsan',
'PrintName': function() {
let fun = () => {
return this.name;
};
return fun();
}
};
console.log(Person.PrintName()); // 输出: zhangsan
2.4 类
类是对象的模板,对象是类的实例。不同于JavaScript使用函数和基于原型的继承,在TypeScript中是基于类的继承并且对象是由类构建出来的。
2.4.1 属性和方法
在TypeScript中,一般变量用let,常量用const,变量后面要声明类型,也可以省略不写,而创建方法则需要在类中,默认为public,返回值在方法名后面加冒号,无返回值可省略或写为void。
// 属性示例
let name1 : string = 'zhangsan';
let name2 = 'zhangsan';
const name3 = 'zhangsan';
// 方法示例
class User{
getUserName():string{
return 'zhangsan';
}
}
let user = new User();
console.log(user.getUserName());
2.4.2 类的继承
TypeScript中通过extends关键字实现继承,派生类通常被称为子类,基类通常被称为父类,子类中使用super来调用父类的构造函数和方法。
class Person{
name:string;
constructor(name:string){
this.name = name;
}
}
class Student extends Person{
constructor(name:string){
super(name);
}
}
let stu = new Student('zhangsan');
console.log(stu.name); // zhangsan
2.4.3 类的实现接口
实现(Implement)是TypeScript 中的重要概念,一般用于实现接口(Interface)。通常一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口。当一个类实现了某个接口时,它就必须实现该接口中定义的所有属性和方法。这有助于确保类具有所需的结构和行为,并且可以在编译时发现错误。
// 定义一个接口
interface Student{
sayHello():void;
}
// 定义一个类,实现了接口Student
class Person implements Student{
sayHello(){
console.log('hello');
}
}
// 创建一个名为stu的Person类的实例
let stu = new Person();
stu.sayHello();
2.4.4 权限修饰符
public 、private、protected、readonly。
1. public
默认修饰符,表示类和子类任意一个都能访问到。
class Student{
public name:string;
constructor(name:string){
this.name = name;
}
}
let stu = new Student('zhangsan');
console.log(stu.name); // zhangsan
2. private
表示成员是私有的,只能在当前类中访问。
class Animal {
private name: string; // private 成员
constructor(name: string) {
this.name = name; // ✅ 内部访问:类内部可直接访问
}
public getName() {
return this.name; // ✅ 内部访问:类内部的方法中可访问
}
}
class Dog extends Animal {
public getDogName() {
return this.name; // ❌ 报错:属性“name”为私有属性,只能在类“Animal”中访问。
}
}
// 外部访问
const dog = new Dog('Buddy');
console.log(dog.name); // ❌ 报错:属性“name”为私有属性,只能在类“Animal”中访问。
console.log(dog.getName()); // ✅ 允许:通过公共方法间接访问
3. protected
受保护,只能在当前类和子类的内部中访问。
class Animal {
private name: string; // private 成员
constructor(name: string) {
this.name = name; // ✅ 内部访问:类内部可直接访问
}
public getName() {
return this.name; // ✅ 内部访问:类内部的方法中可访问
}
}
class Dog extends Animal {
public getDogName() {
return this.name; // ❌ 报错:属性“name”为私有属性,只能在类“Animal”中访问。
}
}
// 外部访问
const dog = new Dog('Buddy');
console.log(dog.name); // ❌ 报错:属性“name”为私有属性,只能在类“Animal”中访问。
console.log(dog.getName()); // ✅ 允许:通过公共方法间接访问
4. readonly
表示只读,不能修改。
class Student{
readonly name:string;
constructor(name:string){
this.name = name;
}
}
let stu = new Student('zhangsan');
console.log(stu.name);
stu.name = 'lisi'; //无法为“name”赋值,因为它是只读属性。
2.5 泛型
在创建自己的项目时,使用的组件不仅要考虑当前的数据类型,而且应该支持未来可能会加入的数据类型,这样在开发大型系统时才会更加灵活,复用性更高。从复用性等架构层面来思考问题,就需要使用泛型了。
2.5.1 泛型示例
首先创造一个简单的示例。现在需要一个函数,用来返回传入值,加入传入值是number类型,则代码如下:
function fn(num:number){
return num;
}
这样会产生一个问题,因为声明的类型是number,所以传入其他类型的数据会报错。为解决这个问题,可以这样修改:
function fn(num:any){
return num;
}
问题虽然解决了,但是又带来了一个新问题。使用any类型虽然可以让这个函数正常工作,但是无法知道传入类型和返回类型是否相同,例如传入一个number,但是返回的是any,则无法确定放回的就是number。这时候就需要使用类型推论做第二次优化:
function fn<T>(num:T):T{
return num;
}
console.log(fn(5)); // 5
console.log(typeof fn(5)); // number
console.log(fn('sttat')); // sttat
console.log(typeof fn('sttat')) // string
示例中给函数添加了类型变量T,编译器会根据传入的参数自动确定T的类型,然后把这个类型设置给返回值,这样就可以确定参数类型与返回值类型是否相同了。
2.5.2 泛型接口
泛型接口可以用来定义可以使用于多种类型的接口,该接口可以在不必明确类型的情况下定义复杂的数据类型。假设我们要定义一个接口,表示一个包含数据的对象,但数据是任意类型的。
// 不使用泛型,则需要重复定义
interface StringContainer{
data:string;
}
interface NumberContainer{
data:Number;
}
// ...
// 使用泛型,只需定义一次,调用时再修改即可
interface Container<T>{
data:T;
}
const strContainer:Container<string> = {data:'hello'};
const numContainer:Container<Number> = {data:123};
上述代码中首先定义了一个Student的泛型接口,该接口接收两个类型变量T和U,并定义了两个属性。然后在getinfo函数内部创建一个stuInfo对象,该对象符合Student接口的定义,并通过接收的参数进行初始化。最后输出信息。
2.5.3 泛型类
TypeScript 的泛型类是带有一个或多个类型变量的类,可以用来创建适用于多种类型的类。假设我们需要创建一个可以存放任意类型的Box类。
// 不使用泛型,则需要重复定义
class Box{
value:string;
constructor(value:string){
this.value=value;
}
getVul():string{
return this.value;
}
}
// 使用泛型,则支持任意类型
class Box1<T>{
value:T;
constructor(value:T){
this.value=value;
}
getVul():T{
return this.value;
}
}
const strbox = new Box1<string>('hello');
const numbox = new Box1<Number>(123);
2.5.4 泛型约束
在函数内部使用泛型变量时,由于事先不知道它是那种类型,所以不能随意操作它的属性和方法。比如一下代码:
// 定义一个泛型函数
function getVal<T>(value:T){
console.log(value.length);
return value;
}
// 报错:Property 'length' does not exist on type 'T'.
显然,编译器不确定T类型是否有length属性,所以直接报错,因此有必要对泛型进行约束,如只允许这个函数传入包含length属性的变量。
// 定义一个接口Lengthwise 具有一个length属性
interface Lengthwise{
length:number;
}
// 定义一个泛型函数,受限于接口Lengthwise
function getVal<T extends Lengthwise>(value:T){
console.log(value.length);
return value;
}
getVal('aaaaa'); // 5
getVal(true); // 报错: Argument of type 'boolean' is not assignable to parameter of type 'Lengthwise'.
2.6 交叉类型和联合类型
前面讲述了基础数据类型,下面介绍下高级类型。包含交叉类型(Intersection Types)和 联合类型(Union Types)。
2.6.1 交叉类型
指将多个类型合并为一个类型,这样的类型具有所有输入类型的特征,并且它们的实例可以同时具有多个类型的所有属性和方法。
语法:type IntersectionType = TypeA & TypeB,可以用 & 运算符合并多个类型。
// 定义一个接口 Person
interface Person{
name:string;
age:number;
}
// 定义一个接口 Student
interface Student{
stuId:string;
score:number;
}
// 使用交叉类型创建一个新的类型PersonStudent
type PersonStudent = Person & Student;
// 创建一个personstudent的对象
const personstudent: PersonStudent={
name:'zhangsan',
age:12,
stuId:'000001',
score:112,
}
2.6.2 联合类型
联合类型表示一个值可以是几种类型之一。
语法:type Union Types = TypeA | TypeB
// 定义一个名为test的变量,类型为string或number
let test:string|number = 'hello';
test = 123;
// 定义一个名为value的变量,类型为string或number数组
let value:string|number[] = 'hello';
value = [1,2,3];
3. Vue 的基本指令
本章的示例代码将会使用CDN的形式编写,使用方法可见1.3.5章节。
3.1 Mustache 语法
Vue 使用一种基于HTML的模板语法,能够声明式地将其组件实例地数据绑定到呈现地DOM上,所有地Vue模板都是语法层面合法的HTML,可以被符合规范的浏览器和HTML解析器解析。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<span>{{name}}</span>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
}
}
}).mount('#app')
</script>
Mustache 语法就是用双花括号包裹变量的写法,span标签中的双花括号包裹了name,就会自动与data中的同名那么所绑定。读者可以自行尝试,后面的章节将频繁使用这种语法。
3.2 常用指令
3.2.1 v-if 指令
类似于其他编程语言的 if else if else,Vue在HTML中也实现了条件判断渲染。
<body>
<!-- v-if的使用 -->
<div id="app">
<div v-if="myColor == 'blue'">蓝色</div>
<div v-if="myColor == 'red'">红色</div>
<div v-if="myColor == 'green'">绿色</div>
<div v-if="myColor == 'yellow'">黄色</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'red',
}
}
}).mount('#app')
</script>
v-if可以说是使用频率最高的指令,通常在控制一个元素是否显示的时候使用,也可以搭配v-else-if、v-else一起使用,读者可自行尝试。
3.2.2 v-show 指令
v-show 语法与 v-if 基本类似,但是没有提供 else if 和 else 等语句。把前面示例中的myColor的值修改为 blue。
<body>
<!-- v-show的使用 -->
<div id="app">
<div v-show="myColor == 'blue'">蓝色</div>
<div v-show="myColor == 'red'">红色</div>
<div v-show="myColor == 'green'">绿色</div>
<div v-show="myColor == 'yellow'">黄色</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
}
}
}).mount('#app')
</script>
v-if 和 v-show 的区别在于:v-if 的值如果为假,那么其元素内部的内容就不会被渲染;v-show的值如果为假,那么该元素的display属性会被设置为none。
在需要频繁切换显示的情况使用v-show,其它情况则用v-if。
3.2.3 v-for 指令
Vue 在HTML中不仅实现了条件判断渲染,同样也实现了列表循环渲染,也就是 v-for 语句,该指令要使用 v-for = "item in itmes" 形式的语法。
<body>
<!-- v-for 的使用 -->
<div id="app">
<div v-for="fruit in fruits">
{{fruit}}
</div>
<div v-for="(fruit,index) in fruits">
{{index + 1 + fruit}}
</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
}
}
}).mount('#app')
</script>
3.2.4 v-text 指令
前面三个指令在开发中运用较多,后买你的指令虽然使用频率低一些,但是仍然需要熟练掌握。使用v-text 绑定值的效果与双花括号一样。v-text 会覆盖元素中的所有内容。
<body>
<!-- v-text 的使用 -->
<div id="app">
<div>{{myColor}}</div>
<div v-text="myColor">我不会被显示</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
}
}
}).mount('#app')
</script>
3.2.5 v-pre 指令
前面学习了使用双花括号的Mustache语法,明白了在双花括号内的属性都会被转义,那如果我想正常的显示双花括号呢?从而让里面的内容不被转义,这时使用v-pre就可以了。
<body>
<!-- v-pre 的使用 -->
<div id="app">
<div v-pre>{{myColor}}</div>
<div v-text="myColor">我不会被显示</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
}
}
}).mount('#app')
</script>
3.2.6 v-cloak 指令
在使用Vue开发的过程中,如果一个页面的数据量很大并且使用了大量的数据绑定,那么可能出现一个问题:用户会看到还没编译完成的双花括号标签,直到完全加载完毕才会显示实际渲染的内容。要解决这个问题,可以使用v-cloak指令。
输入以下代码,注意新建一个<style>标签来存放样式:
<style>
[v-cloak]{
display: none;
}
</style>
<body>
<!-- v-cloak 的使用 -->
<div id="app">
<div v-cloak>
{{myColor}}
</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
}
}
}).mount('#app')
</script>
这个指令主要用来解决加载过程中的显示问题,可以自定义style 属性来解决。
3.2.7 v-html 指令
它绑定的内容会直接作为普通的HTML插入,需要注意的是双花括号语法不会被解析。
<body>
<!-- v-html 的使用 -->
<div id="app">
<div v-html="h1"></div>
<div v-html="h2"></div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
h1:'<h2>hello world</h2>',
h2:'<h2>hello Vue {{myColor}}</h2>',
}
}
}).mount('#app')
</script>
3.2.8 v-once 指令
意思就是这段代码只会被解析一次。
<body>
<!-- v-once 的使用 -->
<div id="app">
<div>年龄:{{age}}</div>
<div v-once>年龄:{{age}}</div>
<button @click="addAge">增加年龄</button>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
h1:'<h2>hello world</h2>',
h2:'<h2>hello Vue {{myColor}}</h2>',
age:12,
}
},
methods: {
addAge(){
this.age++
}
},
}).mount('#app')
</script>
可以看到,点击按钮,第一个年龄会不断增加,第二个则没有变化。
3.2.9 v-on 指令
v-on 的作用就是绑定事件监听器。例如,按钮点击事件、输入框的监听等都属于事件。上一节使用的 @click 就是v-on 的使用方式之一。下面给出几个例子:
<body>
<!-- v-on 的使用 -->
<div id="app">
<div>{{age}}</div><br>
<button v-on:click="addAge">增加年龄</button>
<button v-on:click="reduceAge">减少年龄</button>
<!-- 我只会生效一次 -->
<button v-on:click.once="reduceAge">减少年龄</button> <br>
<!-- 输入内容后按下回车确认 -->
<input type="text" @keyup.enter="onEnter">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'blue',
fruits:['aaa','bbb','ccc'],
h1:'<h2>hello world</h2>',
h2:'<h2>hello Vue {{myColor}}</h2>',
age:12,
}
},
methods: {
addAge(){
this.age++
},
reduceAge(){
this.age--
},
onEnter(event){
console.log('我输入的值为:',event.target.value)
}
},
}).mount('#app')
</script>
3.2.10 v-bind 指令
v-bind 可以动态的绑定一个或多个Class和Style等属性。
<style>
.red-div{
background-color: red;
}
</style>
<body>
<!-- v-bind 的使用 -->
<div id="app">
<div v-bind:class="{'red-div':myColor=='red'}">v-bind 绑定class</div>
<div :class="{'red-div':isRed}">v-bind绑定class简写</div>
<img :src="imgUrl" alt="" :style="{width: size+'px'}">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
myColor:'red',
isRed:true,
imgUrl:'https://p3-search.byteimg.com/img/labis/cd1b6e47b67c0699d0b9e24c53b04908~480x480.JPEG',
size:500
}
},
methods: {
},
}).mount('#app')
</script>
3.3 v-model
开发中,表单提交是一个常见的事件,我们经常要使用input标签,并给input标签进行值绑定,并添加事件监听器监测内容的变化情况,这种写法不仅麻烦,而且加大了出错的概率。 v-model简化了这个步骤,只需要使用少量的代码就可以完成这个操作。不仅input标签,在textarea和select标签上都可以使用v-model,并且在掌握了组件的知识后,还可以在自定义组件上使用v-model。
3.3.1 v-model 基础用法
v-model 本质上是一个语法糖,相当于使用了v-bind和v-on。拿input标签举例,v-model会使用v-bind绑定value属性,再使用v-on监听输入事件。
新建一个v-model.html文件,生成HTML5代码并引入CDN,最后输入以下代码:
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- v-model的使用 -->
<div>我的名字:{{name}}</div>
<input type="text"
:value="name"
@input="event=>name=event.target.value"
>
<br>
<input type="text" v-model="name">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
}
}
}).mount('#app')
</script>
如上图,在输入框中输入内容可以改变名字的内容。两个输入框都与name这一属性进行了绑定,修改一个另外一个也会发生变化。第一种是传统的绑定方式,分别绑定属性值和监听事件;第二种使用v-model进行双向绑定。代码量是不是小很多呢?
v-model的使用还可以更复杂一些,看个例子,为select标签实现双向绑定。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- v-model的使用 -->
<div>我的性别:{{sex}}</div>
<select v-model="sex">
<option value="" disabled>请选择</option>
<option value="男">男</option>
<option value="女">女</option>
</select>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
sex:'',
}
}
}).mount('#app')
</script>
3.3.2 v-model 修饰符
v-model提供了三种修饰符,分别是lazy、number和trim。
(1)lazy 修饰符
lazy 修饰符会在每次input事件后才更新数据。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- v-model的使用 -->
<div>我的名字:{{name}}</div>
<input type="text"
:value="name"
@input="event=>name=event.target.value"
>
<br>
<input type="text" v-model.lazy="name">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
}
}
}).mount('#app')
</script>
对于第二个输入框,输入内容后,只有当鼠标点击其他地方后,名字的属性值才会发生变化。
(2)number 修饰符
会自动将用户输入的内容转换为数字类型。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- v-model的使用 -->
<div>我的年龄:{{age}}</div>
<input v-model="age">
<br>
<input type="text" v-model.number="age">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
age:18,
}
},
watch:{
age(value){
console.log(typeof value)
}
}
}).mount('#app')
</script>
第一个输入框没有绑定number修饰符,输入的内容是字符串,第二个输入框绑定了,输入的内容被转换成了数字。当然,也可以为input的type属性设置为“type=number”,但是这样就没办法输入字符串了。
(3)trim 修饰符
trim 会自动去除输入内容两端的空格。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- v-model的使用 -->
<div>我的职业{{profession}}</div>
<input v-model="profession">
<br>
<input type="text" v-model.trim="profession">
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
name:'zhangsan',
age:18,
profession: ' teacher',
}
},
}).mount('#app')
</script>
不管左右两侧输入多少个空格,当输入框失去焦点或按下回车时,空格都会被去除。
4. CSS 样式绑定
4.1 Class 属性绑定
4.1.1 绑定对象
首先新建一个文件4-1.html,和之前一样导入代码。下面开始编写案例。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<div :Class="{'red-div':isRed}">Class绑定对象</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
isRed:true,
}
}
}).mount('#app')
</script>
<style>
.red-div{
background-color: red;
}
</style>
上面的代码在模板中使用了:Class绑定对象的语法,将一个对象传递给Class属性,对象的键为类名,值为true或flase。当isRed为true时,对象中的red-div键的值为true,因此div元素具有red-div类带来的样式。
4.1.2 绑定计算属性
如果绑定的条件比较复杂,也可以通过计算属性来实现。只要在export default中添加一个computed属性,并把要设置为计算属性的参数填写在里面即可。继续修改4-1.html的代码。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- 通过isRed判断是否绑定red-div -->
<div :Class="{'red-div':isRed}">Class绑定对象</div>
<!-- 绑定classobject中的Class -->
<div :Class="ClassObject">Class绑定计算属性</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
isRed:true,
isWhite:true,
}
},
computed:{
ClassObject(){
console.log('可以在这里写逻辑代码');
return{
'red-div':this.isRed,
'white-text':this.isWhite,
}
}
},
methods:{
}
}).mount('#app')
</script>
<style>
.red-div{
background-color: red;
}
.white-text{
color: white;
}
</style>
绑定关系链:
数据(isWhite) → 计算属性(ClassObject) → HTML元素(class属性) → CSS样式
第二个div通过类绑定了ClassObject,并且ClassObject会返回一个对象,对象中的属性值又被绑定了style样式,当这些属性为true时,在页面中就可以看到变化。
Class绑定到了一个名为ClassObject的计算属性上,计算属性与方法不同,放在computed中而非methods中,它可以根据值得变化重新计算结果并渲染到div上。现在只需了解computed的用法,其原理在后面的章节会详细介绍。
4.1.3 绑定数组
如果有多个 Class 需要绑定,则可以使用方括号进行数组绑定,可以在里面添加条件判断。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- 通过isRed判断是否绑定red-div -->
<div :Class="{'red-div':isRed}">Class绑定对象</div>
<!-- 绑定classobject中的Class -->
<div :Class="ClassObject">Class绑定计算属性</div>
<!-- 同时绑定redClass和whiteClass -->
<div :Class="[redClass,whiteClass]">Class绑定数组1</div>
<!-- 添加判断的混合绑定 -->
<div :Class="[{'bold-text':isBold},redClass]">Class绑定计算属性</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
isRed:true,
isWhite:true,
isBold:true,
redClass:'red-div',
whiteClass:'white-text',
}
},
computed:{
ClassObject(){
console.log('可以在这里写逻辑代码');
return{
'red-div':this.isRed,
'white-text':this.isWhite,
}
}
},
methods:{
}
}).mount('#app')
</script>
<style>
.red-div{
background-color: red;
}
.white-text{
color: white;
}
.bold-text{
font-weight: bold;
}
</style>
第一个示例中,使用了一个由redClass和whiteClass组成的绑定数组,这两个变量在data中定义,当Vue加载时,这个div将获得red-div和white-div这两个类名。
4.2 Style 属性绑定
Style属性绑定是一种快速且简单的方法,可以为HTML元素定义样式,但不够灵活,因为不能在多个元素上重复使用相同的样式规则。
4.2.1 绑定对象
Style 属性绑定的对象语法十分直观——看着非常像CSS,但其实是一个JavaScript对象。
新建一个文件4.2.html,生成HTML5代码并引入CDN。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- 把fontsize的值传递给font-size -->
<div :style="{'font-size':fontsize}">Style对象绑定1</div>
<!-- 传递整个styleObect -->
<div :style="styleObject">Style对象绑定2</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
fontsize:'14px',
styleObject:{
'font-size':'17px',
color:'red',
}
}
}
}).mount('#app')
</script>
上面的代码包含两个div元素,分别使用了不同方式的Style绑定对象。
第一个Style讲font-size属性绑定到data的fontsize属性上,当Vue加载时,style元素讲具有一个决定字体大小的font-size属性值。
第二个Style直接绑定在了data的styleObject属性上,这个样式对象包含一个font-size和color,分别控制字体大小和颜色。
4.2.2 绑定数组
使用方括号进行数组绑定,数组绑定时可以搭配花括号来使用,在其中添加条件判断。
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<!-- 把fontsize的值传递给font-size -->
<div :style="{'font-size':fontsize}">Style对象绑定1</div>
<!-- 传递整个styleObect -->
<div :style="styleObject">Style对象绑定2</div>
<!-- 同时绑定styleObject和backColor -->
<div :style="[styleObject,backColor]">Style绑定数组1</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
fontsize:'14px',
styleObject:{
'font-size':'17px',
color:'red',
},
backColor:{
'background-color':'blue',
}
}
}
}).mount('#app')
</script>
4.2.3 自动前缀与样式多样
当使用浏览器特有前缀的CSS属性时,Vue会自动为他们加上相应的前缀,Vue在运行时检查某个属性是否可以在当前浏览器中使用,如果浏览器不支持这个属性,那么将尝试加上各个浏览器支持的特殊前缀。
主流浏览器引擎前缀如下:
- -webkit- 谷歌、Safari、新版Opera、iOS系统的浏览器、其他基于WebKit内核的小众浏览器;
- -moz- 火狐浏览器;
- -o- 旧版Opera;
- -ms- IE浏览器和Edge浏览器。
样式多值指的是:对一个样式属性提供多个(不同前缀的)值,示例如下:
<div :style="{display:['-webkit-box','-ms-flexbox','flex']}"></div>
数组仅会渲染浏览器支持的最后一个值,在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display:flex。
4.3 CSS 预处理器
CSS 预处理器是一种用于简化CSS代码编写的工具,可以帮助开发者更高效的管理和维护CSS代码。它们基于CSS的语法添加了一些额外的功能,如变量、嵌套、函数和混合等。目前比较流行的CSS预处理器有Sass,Less,Stylus。
4.3.1 使用Sass
CSS 预处理器的种类比较多,但是大同小异,用法也极其相似,因此本节选择最常用的Sass。
下面介绍如何在Vue3 中使用Sass。
(1)安装Sass依赖,可以使用NPM或YARN安装:
#使用npm安装
npm install sass sass-loader -D
#使用yarn安装
yarn add sass sass-loader -D
(2)在Webpack配置文件中配置sass-loader,例如,在Vue CLI生成的项目中,可以在vue.config.js 文件中添加如下代码:
module.exports = {
css:{
loaderOptions:{
sass:{
additionalData:'@import "@/assets/styles/variables.scss";'
}
}
}
}
在之前创建的hello-vue文件中,已经存在了部分代码,也可以这样修改:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
css: {
loaderOptions: {
sass: {
additionalData: '@import "@/assets/styles/variables.scss";'
}
}
}
})
(3)在项目中创建一个.scss文件,如style.scss,然后可以在其中编写Sass代码,在.vue组件中,可以使用<style>标签来引入样式文件:
<template>
<div class="container">
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
message: 'Welcome to my Vue 3 app!'
}
}
}
</script>
<style lang="scss" scoped>
.container {
h1 {
color: $primary-color; // 使用Sass变量
}
}
</style>
上面同时出现了Sass和Scss,区别在于,Sass是一种CSS预处理器,而Scss是Sass3 引入的新语法,是Sass 的一种语法格式。
4.3.2 嵌套写法
Sass 的嵌套语法是指在Sass中可以使用嵌套的方式来描述样式规则的关系。示例如下:
// css
.container{
width: 100%;
}
.container .title{
font-size: 24px;
}
.container .content{
font-size: 16px;
}
// sass
.container{
width: 100%;
.title{
font-size: 24px;
}
.content{
font-size: 16px;
}
}
上面代码中,定义了一个container样式,并在其中嵌套了两个子元素title和content样式。
使用 & 选择器:
// css
.button{
background-color: blue;
}
.button:hover{
background-color: red;
}
// sass
.button{
background-color: blue;
&:hover{
background-color: red;
}
}
示例中,& 符号代表button本身,这样可以简化代码。
4.3.3 定义变量
定义变量可以在整个样式表中重复使用,方便简介、易于更新和维护。
在Sass中定义变量的语法时“$”符号,后面跟变量名和变量值。
$primary-color : #007bff
.button{ background-color : $primary-color}
除了颜色类型的变量定义外,Sass还支持其他类型的变量,例如:
// 数字类型
$font-size : 16px;
// 字符串类型
$font-family : 'Helvetica Neue', sans-serif;
// 布尔类型
$debug-mode:true;
// 列表类型
$border-widths:1px 2px 3px 4px;
// Map类型
$colors:(primary:#007bff,secondary:#6c757d)
4.3.4 模块系统
Sass的模块系统是一种组织样式代码的方式,它允许开发者将样式表分解成多个文件,并使用@import指令将这些文件组合在一起,形成一个完整的样式表。
Sass的模块系统包括两个概念:模块和导入。其中,模块就是一个Sass文件,它可以包含一些变量、函数、混合器、样式规则等内容;而导入则是将一个模块中的内容引入另一个模块中使用。例如,导入一个Sass模块:
@import 'reset'
4.3.5 混合指令
Sass的混合指令(mixin)是一种可以重复使用的样式代码块,它可以包含一些CSS属性和值,也可以包含Sass变量、函数和逻辑控制语句等内容。
Sass的混合指令使用@mixin关键字定义:
@mixin button{
display:inline-block;
padding:0.5rem 1rem;
font-size: 1rem;
line-height: 1.5;
color: #fff;
}
.button{
@include button;
}
上面定义了一个名为button的混合指令,包含一些常用的样式,并且使用@include将其插入了.button元素的样式中,可以避免在多个样式规则中重复定义相同的样式代码。
除了基本的混合指令外,Sass还支持带参数的混合指令,可以通过传递不同的参数来生成不同的样式代码,如下:
@mixin button($bg-color){
background-color: $bg-color;
}
.button-primary{
@include button(#007bff);
}
.button-secondary{
@include button(#6c757d)
}
4.3.6 样式继承
Sass的样式继承使用@extend 指令来实现,可以将一个选择器的样式规则继承到另一个选择器中。
.button{
display:inline-block;
padding: 0.5rem 1rem;
}
.button-primary{
@extend .button;
}
.button-secondary{
@extend .button;
background-color: #6c757d;
}
4.4 综合案例:计算器的实现
计算器包括数字输入、加、减、乘、除、清空、显示结果等功能,用这个样例来复习前面介绍的指令和样式绑定等功能。
新建一个文件Calculator.html,生成一段HTML5代码并引入CDN。完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue 3 的脚本 -->
<script src="https://unpkg.com/vue@3"></script>
</head>
<style>
#app{
background: #f5f7fa;
padding: 20px;
border-radius: 12px;
box-shadow:0 4px 12px rgba(0,0,0,0.1);
width: fit-content;
}
.display-div{
display: flex;
gap: 8px; /* 按钮间距 */
margin: 6px 0;
padding: 8px;
background: rgba(255,255,255,0.9);
border-radius: 8px;
}
.calc-display{
background: #e9eff4;
padding: 12px;
margin: 12px 0;
border-radius: 6px;
font-size: 18px;
color: #666;
min-height: 24px;
text-align: right;
}
.result-display{
background: #ffffff;
padding: 12px;
margin: 12px 0;
border-radius: 6px;
font-size: 24px;
font-weight: bold;
color: #2c3e50;
text-align: right;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
}
.button{
width: 80px;
height: 80px;
font-size: 30px;
}
</style>
<body>
<!-- 创建一个ID为“app”的div元素,其将被在Vue中使用 -->
<div id="app">
<div class="result-display">{{result}}</div>
<div class="calc-display">{{Text||'0'}}</div>
<div class="display-div">
<button class="button" @click="appendText('1')">1</button>
<button class="button" @click="appendText('2')">2</button>
<button class="button" @click="appendText('3')">3</button>
<button class="button" @click="appendText('/')">/</button>
</div>
<div class="display-div">
<button class="button" @click="appendText('4')">4</button>
<button class="button" @click="appendText('5')">5</button>
<button class="button" @click="appendText('6')">6</button>
<button class="button" @click="appendText('*')">*</button>
</div>
<div class="display-div">
<button class="button" @click="appendText('7')">7</button>
<button class="button" @click="appendText('8')">8</button>
<button class="button" @click="appendText('9')">9</button>
<button class="button" @click="appendText('-')">-</button>
</div>
<div class="display-div">
<button class="button" @click="clearText('C')">C</button>
<button class="button" @click="appendText('0')">0</button>
<button class="button" @click="calculate('=')">=</button>
<button class="button" @click="appendText('+')">+</button>
</div>
</div>
</body>
<script>
// 从Vue 中解构出createApp函数
const{createApp} = Vue
// 创建一个Vue应用
createApp({
// data函数,用于定义应用中的数据
data(){
return{
result:'',
Text:'',
}
},
methods:{
appendText(value){
this.Text += value;
},
clearText(){
this.Text = '';
this.result='';
},
calculate(){
try{
if(['+','*','/','-'].includes(this.Text.slice(-1))){
this.Text = this.Text.slice(0,-1);
}
this.result=eval(this.Text).toString();
} catch(error){
this.result = 'Error';
}
}
}
}).mount('#app')
</script>
</html>