TypeScript 是一种由 Microsoft 开发的编程语言,是 JavaScript 的超集。Typescript是强类型语言,主要添加了静态类型系统,这使得开发大型应用程序时可以更容易地发现和修复错误。对于我们初学者来说,首先来了解下 TypeScript 的基本概念和示例:
类型
基础类型:
number
:用于表示数值,例如let age: number = 30;
string
:用于表示字符串,例如let name: string = 'Alice';
boolean
:用于表示布尔值,例如let isActive: boolean = true;
array
:数组,例如let numbers: number[] = [1, 2, 3];
tuple
:元组,例如let person: [string, number] = ['Alice', 30];
enum
:枚举类型,例如:enum Color { Red, Green, Blue } let c: Color = Color.Green;
any、unknow与never
-
any
类型:- 可以赋值为任何类型的值,且可以赋值给其他类型。
- 使用
any
类型时,TypeScript 编译器不会对这个值进行类型检查,这样会丧失类型安全性。 - 例子:
let value: any = 5; value = "Hello"; let anotherValue: number = value; // 允许,因为 value 的类型是 any
-
unknown
类型:- 也可以赋值为任何类型的值,但是在赋值给其他类型之前需要进行类型检查。
- 不能直接将
unknown
类型赋给其他类型,因为这可能会导致类型错误。 - 例子:
let value: unknown = 5; value = "Hello"; // 直接赋值会报错 // let anotherValue: number = value; // 错误 // 必须先进行类型检查 if (typeof value === 'string') { let stringValue: string = value; // 允许,因为我们已经检查了类型 }
-
对
unknown
类型的变量进行属性访问:- 不可以直接访问
unknown
类型的变量上的属性,因为 TypeScript 编译器无法保证这些属性存在。 - 需要先进行类型检查或类型断言,确保你可以安全地访问属性。
- 例子:
let value: unknown = { name: "Alice" }; // 直接访问属性会报错 // let name = value.name; // 错误 // 先进行类型检查 if (typeof value === 'object' && value !== null && 'name' in value) { let name = (value as { name: string }).name; // 允许 }
- 不可以直接访问
unknown
更注重类型安全,需要进行显式的类型检查,而 any
是一种更加宽松的类型,但它会降低类型安全性。
never
类型
never
是TypeScript中的一个特殊类型,用来表示永远不会有值的类型。绝大多数用在函数上。
使用场景:
==>1. 函数不返回任何值
如果一个函数永远不会返回(抛出异常),我们就可以将它的返回类型指定为never
function throwError(message: string): never {
throw new Error(message);
}
==> 2. 函数无法正常完成
如果一个函数执行了一个无限循环或类似的操作,导致它无法正常结束,它的返回类型也可以是 never
`function infiniteLoop(): never {
while (true) {
// 无限循环
}
}`
字面量类型
比如,在TS中,let x: 'hello'
就是一种字面量类型的用法。表示变量 x
的值只能是字符串 'hello'
。
例子及解释:
let x: 'hello' = 'hello'; // 合法,'hello' 是变量 x 的唯一允许值
如果我们将 x
赋值为其他字符串或任何其他类型的值,TypeScript 会报错:
x = 'world';
x = 42;
这种类型的定义创建了一个字面量类型,是一种更为具体的类型,限制变量只能取特定的值。字面量类型可以用于各种场景,例如:
- 函数参数和返回值:
- 限制函数参数只能接受特定的值。
- 例子:
function greet(greeting: 'hello' | 'hi') { console.log(greeting); } greet('hello'); // 允许 greet('hi'); // 允许 greet('goodbye'); // 错误
-
创建常量枚举:
- 用于创建具有特定值的常量。
- 例子:
const STATUS_OK: 'OK' = 'OK'; const STATUS_ERROR: 'ERROR' = 'ERROR'; function checkStatus(status: 'OK' | 'ERROR') { if (status === STATUS_OK) { console.log('Everything is fine!'); } else { console.log('There was an error.'); } } checkStatus(STATUS_OK); // 允许 checkStatus('OK'); // 允许
-
作为类型的一部分:
- 可以组合字面量类型和其他类型创建更复杂的类型。
- 例子:
type Status = 'success' | 'failure'; function handleStatus(status: Status) { switch (status) { case 'success': console.log('Operation was successful!'); break; case 'failure': console.log('Operation failed.'); break; } } handleStatus('success'); // 允许 handleStatus('failure'); // 允许
类型推断:
- TypeScript 可以根据变量的值自动推断类型,例如:
let count = 10; // TypeScript 推断 count 的类型为 number
ps:TS类型检查发生在编译阶段
联合类型:
- 允许一个值是几种类型之一,例如:
function formatId(id: number | string): string { return `ID: ${id}`; }
类型别名:
- 可以创建类型的别名,例如:
type ID = number | string; function formatId(id: ID): string { return `ID: ${id}`; }
函数
函数类型:
当我们声明函数时,如果函数中没有返回值,则为空
如果有返回值,则类型为返回值的类型
如果我们已经声明此函数的类型为void,则不能有返回值:
参数为函数
我们看这个例子:
这里给出了提示,说参数的类型可以更精确,如果是在JS当中,我们直接这样写没有任何问题,但在TS中,类型的定义必须明确,所以这里补上:
function foo(f:Function){
}
function bar(){
}
foo(bar)
参数不确定
如果我们定义的函数,不确定是否有参数的传入,我们可以加上?
代表不确定。
其他例子:
例子 1
function foo(x: number = 1, y?: number): [number, number?] {
return [x, y];
}
foo(1, 2);
解释:
- 该函数
foo
有两个参数x
和y
,其中x
有默认值1
,y
是可选参数。 - 返回一个元组
[x, y]
,其中y
是可选的,所以可以省略。 - 当调用
foo(1, 2)
时,返回[1, 2]
。
例子 2
function foo({a, b, c}: {a: number, b: number, c: number}) {
}
foo({a: 1, b: 2, c: 3});
解释:
- 该函数接受一个对象作为参数,且对象内有
a
,b
,c
三个必需的属性,且它们的类型都是number
。 - 调用时传入对象
{a: 1, b: 2, c: 3}
,成功匹配。
例子 3
function foo(...arg: [number, number, number]) {
}
foo(1, 2, 3);
解释:
- 该函数使用了 TypeScript 的元组类型
[number, number, number]
来定义一个可变参数...arg
,表示需要传入三个number
类型的值。 - 调用时传入
foo(1, 2, 3)
,函数的arg
参数将会是[1, 2, 3]
。
例子 4
解释:
- 函数的参数
arg
被定义为readonly number[]
,即不可变的数组,因此不能对它进行push
等修改操作。 arg.push(1)
会报错,因为readonly
修饰符禁止修改数组。
函数重载
在 TypeScript 中,函数重载是一种为同一个函数定义多个签名的技术,允许函数根据不同的参数类型执行不同的逻辑。你提供的代码可以通过函数重载来更明确地表达参数和返回类型的关系。
下面是基于简单的 reverse
函数的重载实现:
// 函数签名:用于声明重载
function reverse(str: string): string;
function reverse(arr: number[]): number[];
// 实现函数逻辑
function reverse(strOrArr: string | number[]): string | number[] {
if (typeof strOrArr === 'string') {
return strOrArr.split('').reverse().join(''); // 处理字符串
} else {
return strOrArr.slice().reverse(); // 处理数组
}
}
// 调用函数
const reversedString = reverse('abcdefg'); // 返回 'gfedcba'
const reversedArray = reverse([1, 2, 3]); // 返回 [3, 2, 1]
解释:
-
重载签名:可以为
reverse
函数定义多个重载签名:function reverse(str: string): string;
表示当输入为string
类型时,返回类型也是string
。function reverse(arr: number[]): number[];
表示当输入为number[]
类型时,返回类型是number[]
。
-
实现逻辑:实际的
reverse
函数实现接受一个string
或number[]
类型的参数,通过类型检查(typeof
)决定处理方式。 -
调用:
reverse('abcdefg')
返回一个反转的字符串'gfedcba'
。reverse([1, 2, 3])
返回反转后的数组[3, 2, 1]
。
通过这种方式,TypeScript 能更好地推断返回值的类型,从而在调用时提供更精确的类型提示和检查。