看完本篇文章,自己组织语言回答以下几个问题:
typeScript相关:
- TypeScript是什么?
- TypeScript 类型系统的主要优势?
- TS 和 JS的区别是什么?
泛型相关:
- 泛型是什么?
- 我们为什么需要用泛型?
- 使用泛型典型的应用场景?
> TypeScript类型概述
类型注解:为变量添加类型约束。约定什么类型,就只能给变量赋值该类型的值,不然就会报错。
可以将 TS 中的常用基础类型细分为两类:
- JS 已有类型
- 原始类型,简单类型(
number/string/boolean/null/undefined) - 复杂数据类型(数组,对象,函数等)
- 原始类型,简单类型(
- TS 新增加粗样式类型
- 联合类型
- 自定义类型(类型别名
- 接口
- 元组
- 字面量类型
- 枚举
- void
- ...
1、简单数据类型
let num: number = 18
let str: string = '老师'
let bool: boolean = false
等等...
2、数组类型
// 写法一: 类型[] = [值...]
let arr1: number[] = [1, 3, 5]
// 写法二:Array<类型> = [值.....]
let arr2: Array<string> = ['a', 'b', 'c']
3、联合类型( | )
应用场景:当一个变量需要支持多种类型的赋值时,由两个或多个其他类型组成联合类型,可以是这些类型中的任意一种
// 1. 联合基本类型
let a: number | string = 1
a = '1' // 可以给 a 赋数字和字符串类型
// 2. 联合数组类型
let arr: (number | string)[] = [1, 2, '3'] // 可以放数字和字符串的数组
4、类型别名(type)
应用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
// 用法: type 别名(推荐首字母大写) = 类型
type Mytype = string | number | boolean // 可以是字符串、数字和布尔值类型
let a: Mytype = 1
a = 'a'
a = true
let b: Mytype = 1
......
5、函数类型
函数的类型实际上指的是:函数参数和返回值
//基本使用
// 1. 普通函数
// function 函数名(形参1: 类型=默认值, 形参2:类型=默认值): 返回值类型 { }
function fn1(a: number, b:number=100):number {
return a + b
}
let num = fn1(1, 2)
console.log(num); // 3
// 2. 箭头函数
// const 函数名(形参1: 类型=默认值, 形参2:类型=默认值):返回值类型 => { }
const fn2 = (a: number, b:number=100):number => {
return a + b
}
let num2 = fn2(1) // 第二值是默认值为100 可传可不传
console.log(num2); // 101
// 3. 函数-类型 type
type MyFn = (a: number, b: number) => number
const add: MyFn = (a, b) =>{return a+b}
const sub: MyFn = (x, y) =>{return x+y}
let addNum = add(1, 2)
console.log(addNum);
void 类型:如果一个函数没有返回值,此时在 TS 的类型中,应该使用void类型
// 函数返回值为 void -- 如果函数没有返回值
/** 三种情况
* 1 不写return
* 2 return
* 3 return undefined
*/
function fn3() :void {
console.log('fn3');
}
const fn4 = ():void =>{}
// 自定义函数别名
type MyFnVoid = (a:number, b:number) => void
可选参数(?):使用函数实现某个功能时,参数可以传也可以不传,在给函数参数指定类型时,就用到可选参数了
// 可选参数
// 在参数名的后面添加 ? 代表它可写可不写
function fn5(a?:number, b?:number) {
console.log(a, b);
}
fn5(1, 2)
fn5(1)
fn5()
6、对象类型
JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)
// 1. 基本使用
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let obj:{ name: string; age: number } = { name: 'xiaxia', age: 18 }
// 2. 使用类型别名
type MyStlye = {
// 通过换行来分隔多个属性类型,可以去掉 `;`
name: string
age: number
// 无参数 无返回值
sayHi: () => void
// 参数为number 返回值为string
Hello(count: number):string
}
let obj1: MyStlye = {
name: 'xiaxia',
age: 18,
sayHi() {},
Hello(count: 1) { return '1' }
}
7、接口函数(interface)
应用场景:当一个 对象类型 被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
接口继承--- 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
// 接口用法: 类型名称:Ixxx
interface IStudent {
name: string
age: number
sayHi: () => void
}
let stu1: IStudent = {
name: 'xiaxia',
age: 18,
sayHi(){}
}
// 接口继承 - 公共的属性或方法抽离出来,通过继承来实现复用
interface IPoint2D { x: number; y: number }
const p1: IPoint2D = { x:1, y:2 }
// interface IPoint3D { x: number; y: number; z: number }
// 这里使用继承-简化
interface IPoint3D extends IPoint2D { z: number }
const p2: IPoint3D = { x:1, y:2, z:3 }
/** interface vs type
* 区别1: type-类型别名,只能为对象指定类型;
* interface-接口,不仅可以为对象指定类型,实际上可以为任意类型指定别名
* 区别2: type-类型别名 不可以继承
* interface-接口 可以继承
*/
8、元组类型(Tuple)
- 概述:一种特殊的数组,约定了元素的个数和类型
- 应用场景:经纬度表示地址
// 元组 Tuple
// 需求: 一个数组 两个元素,都是number类型
const positon:[number, number] = [1, 2]
9、字面量类型
- 字面量:
{}[]18'abc'falsefunction(){}
let str1 = 'Hello TS'
let str2 = 'Hello TS'
// 1. str1是什么类型? string类型,必须只能保存字符串
// 2. str2是什么类型? 'Hello TS'字面量类型,只能保存Hello TS
// 可以改写成下面的类型
let str1: string = 'Hello TS'
let str2: 'Hello TS' = 'Hello TS'
// 字面量类型 - 单独使用没有应用场景
// 字面量类型一般和联合类型一起使用
type Direction = 'up' | 'down' | 'left' | 'right'
10、枚举(enum)
使用:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
// 1. 定义枚举 - 大写字母开头
enum Direction { Up, Down, Left, Right }
console.log(Direction);
// {
// '0': 'Up',
// '1': 'Down',
// '2': 'Left',
// '3': 'Right',
// Up: 0,
// Down: 1,
// Left: 2,
// Right: 3
// }
// 2. 使用枚举 枚举名字.xxxx
const d2:Direction = Direction.Up
function fn(a: Direction) {
if(a === Direction.Left){
}
}
// 3. 场景: 后端给的性别是 0(男) 1(女)
enum Gebder { 'man', 'woman' }
const p1:Gebder = Gebder.man // 0
const p2:Gebder = Gebder.woman // 1
- 一个小拓展
其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码,TS编译成JS的代码对照:
// TS
enum Direction { Up, Down, Left, Right }
// JS
// 将这个枚举转换成js后
var Direction = void 0;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
11、any类型
应用场景:向后端发送axios,当不知道返回的值是什么类型时,可以先定义为any,但ts的原则不推荐使用any
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
12、类型推论
// 变量 age 的类型被自动推断为:number
// let age: number = 18
let age = 18
// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
13、类型断言(as)
const aLink = document.getElementById('link')
const aLink = document.getElementById('link') as HTMLAnchorElement
泛型<>
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用
本质: 参数化类型,通俗的讲,就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类,泛型接口、泛型函数。可以通过以下代码理解泛型的本质
// 创建一个 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
function fn(value: number): number { return value }
1. 泛型函数
语法: 在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型
// 泛型函数 ====> 定义一个带泛型变量的函数
// 类型变量 T,是一种特殊类型的变量,它处理类型而不是值
function id<T>(value:T) : T{
return value
}
// 1.1 使用: 尖括号中指定具体的类型
const r1 = id<number>(1)
const r2 = id<boolean>(false)
// 1.2 简化-类型推断
const r3 = id('abc') // String
// 这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全
2. 泛型约束
为什么需要泛型约束?
答: 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性,因为其无法保证传入的类型一定存在某个属性。 比如 length 属性,number 类型就没有 length,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
添加泛型约束的方法: 1 指定更加具体的类型 ;2 添加约束。
2.1 指定更加具体的类型
// 我们这里使用方法也是拿lenght属性来举例
// 将类型修改为 Type[](Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
function id<Type>(value: Type[]): Type[] {
console.log(value.length)
return value
}
2.2 添加约束
// 我们这里使用方法也是拿lenght属性来举例
// 创建一个接口
interface ILength { length: number }
// Type extends ILength 添加泛型约束
// 该约束表示:传入的类型必须具有 length 属性。比如数组
function id<Type extends ILength>(value: Type): Type {
console.log(value.length)
return value
}
3. 泛型类型变量
- 可以有多个
- 并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
// 多个类型变量 - 相互约束
// keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
// 比如: 第二个类型受第一个变量的约束 Key extends keyof Type 代表继承 keyof Type
// keyof Type 是 Type的所有 key值
let obj = { name: 'jack', age: 18 }
// getProp( 对象, 属性名 ) ===> 属性值
// 本示例中 keyof Type 实际上获取的是 obj 对象所有键的联合类型,也就是:'name' | 'age'
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
getProp(obj, 'name')
// 因为 obj 是一个对象,所以这里的 Type 类型为对象类型
4. 泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
// 场景: 定义数组类型时
// 方法一:
let arr1: number[] = [1, 2, 3]
// 方法二:
// 这里的 Array 就是泛型接口
let arr2: Array<string> = ['1', '2', '3']
5. 泛型练习
- 最后来一个泛型小练习检测一下泛型学习的成果: 下面是题目要求以及要改写的代码
// 1. 题目 - useState它接收一个任意类型的数据,返回一个数组。
/** 要求:
* 数组的第一个元素的类型与入参一致;
* 数组的第二个元素是一个函数
* 第二个元素函数的入参类型和返回值类型与useState的入参一致
*/
// 2. 将以下的代码改写成符合上述要求的 Ts 代码
function useState(value) {
const setValue = () => {
}
return [value, setValue]
}
// 测试代码 - 鼠标悬停查看结果
const [num, setNum] = useState(0)
const [str, setStr] = useState('ab')
- 下面是上述代码的答案:自己思考后再看答案,鼠标悬停在测试代码上可以看到符合题目要求的结果
function useState<Type>(value:Type): [Type, (val: Type)=> Type] {
const setValue = (val: Type): Type => {
return val
}
return [value, setValue]
}
// 测试代码 - 鼠标悬停查看结果
const [str, setStr] = useState('123')
const [num, setNum] = useState(123)
总结
本人回答一下文章开头的几个问题,有不同意见的小伙伴可以在评论中发表自己的意见
1. TypeScript是什么?
typescript是微软公司开源的编程语言, type是类型的意思,表示在js的基础之上额外增加了类型系统、提供了类型支持,是js的超集。
2. TypeScript 类型系统的主要优势?
可以显示标记出代码中的意外行为,从而降低了发生错误的可能性
3. TS 和 JS的区别是什么?
TypeScript = Type + JavaScript(在 JS 基础之上,添加了类型支持)
从编程语言的动静来区分:
-
TypeScript 属于静态类型的编程语言,
编译期做类型检查 -
JavaScript 属于动态类型的编程语言,
执行期做类型检查
4. TypeScript怎么安装?
npm i -g typescript
5. 怎么创建支持TS的vue项目?
yarn create vite vite-ts-demo --template vue-ts
泛型
-
泛型是什么?
答: 泛型的本质是参数化类型 ,就是把类型当做参数使用,代码里面就是
<> -
我们为什么需要用泛型?
答:当我们在定义函数时,不知道其传入的参数是什么类型的,我们就可以用泛型去定义它,传入参数是什么类型泛型就是什么
-
使用泛型典型的应用场景?
答:使用泛型典型的应用场景有 泛型类,泛型接口、泛型函数
我就拿泛型接口举个例子:当我们需要创建一个参数为字符串的数组时,我们就可以使用泛型接口
Array<string>这里Array就是泛型接口