void和never的区别
void一般应用于函数的返回值上。它的意思是返回undefined、null。(我们平时不写return,其实就是return undefined)
never也是应用于函数的返回值上。它的意思是不允许返回。注意是不允许return,而不是return undefined。一个函数只要写了return,那返回值类型必然不能是never。
对象作为函数参数时
当一个函数的参数是对象时,ts的校验会有区别:
function person(obj: {firstName: string, lastName: string}): void {
console.log(`${obj.firstName} ${obj.lastName}`);
}
// 传参时构建对象:传入匹配的对象的属性,一个也不多一个也不少,可通过ts类型检测
person({firstName: 'Felix', lastName: 'Luo'}); // test pass
// 传参时构建对象:传入的对象的属性多了或者少了,不可通过ts类型检测
person({firstName: 'Felix', lastName: 'Luo', age: 18}); // test fail
//单独声明一个obj时,传入函数中,如果传入属性多了,可通过检测;如果传入属性少了,不能通过检测
const obj = {
firstName: 'Felix',
lastName: 'Luo',
age: 18,
};
person(obj); // test pass
const obj2 = {
firstName: 'Felix',
}
person(obj2); // test fail
readonly
typescript用来规范一个object中某个属性不允许被覆盖。注意这只是typescript的检验,而不是JavaScript的要求。typescript声明了readonly,是ts的检测,但不是JavaScript中这个对象的这个属性是readonly的。
type User = {
readonly id: number;
username: string;
readonly hobbies: [string];
}
const user: User = {
id: 12345,
username: 'Felix',
hobbies: ['basketball'],
}
console.log(user.id); // correct
// user.id = 12345; // alert
user.hobbies[0] = 'football'; // don't alert
注意:如果声明了一个对象/数组是readonly,那么修改对象/数组里面的值是没问题的,因为对象和数组都是引用类型,只要不修改他们的引用值是不会alert的。
type类型之间的组合
假设我们声明了多个type,但此时我们需要新建一个type,同时包含了前面的几种type。此时我们可以使用type的组合。
type Cat = {
liveNum: number;
}
type Dog = {
breed: string;
}
type CatDog = Cat & Dog & { // combine
color: string;
}
const newAnimal: CatDog = {
liveNum: 9,
breed: 'husky',
color: 'yellow',
}
关于union
string | number 作为参数
在函数内部可以使用if 判断,ts会智能识别if的 typeof 判断!
声明数组时,想要数组内多个类型的值,也可以使用union
type age: (number | string )[] = [1, 2 ,'']
区分:
type age: number[] | string[] = [1, 2 ,'']
literal type
指定值。但单单指定一个数值没什么用,可以配合union一起使用。
type zero: 0 = 0
type isTrue: "true" I "false" I "unknown "
tuples
这个类型是针对于数组的。它让数组的长度、顺序、每个元素的类型都固定。
type HTTPResponse = [number, string]; // 数组的第一位只能是number,第二位只能是string
// 顺序不能变长度也不能变
const goodRes: HTTPResponse = [200, 'ok']; // pass check
const goodRes: HTTPResponse = ['ok', 200]; // fail
const goodRes: HTTPResponse = ['ok', 200, 123]; // fail
// 我们不能更改数组的值为其他类型的值。
goodRes[0] = 300; // pass check
goodRes[1] = 500; // fail
还有一种比较特殊的情况,使用数组的一些方法不会经过ts检测。如
type HTTPResponse = [number, string]; // 数组的第一位只能是number,第二位只能是string
// 顺序不能变长度也不能变
const goodRes: HTTPResponse = [200, 'ok']; // pass check
goodRes.push(666); // pass check
goodRes.pop(); // pass check
goodRes.pop(); // pass check
goodRes.pop(); // pass check
相对来说,tuple会用得比较少。我们其实可以直接使用对象的形式,更加清晰。
enums
我们平时声明常量,就是直接声明,它只是给我们的0,“pending”一个解释的词语。
const PENDING = 0;
const SHIPPED = 1;
const DELIVERED = 2;
if(statusCode === PENDING) {
dosth...
} else if(statusCode === SHIPPED) {
dosth else....
}
那我们解释一下enums是什么。首先这个enums只存在于typescript中。它可以帮助我们声明一串常量,而不需要像上面JS那样定义常量并给其赋值。我们可以把enums就当做一个对象使用。下面举一个简单例子。
// 可供我们使用的常量
enum OrderStatus {
PENDING,
SHIPPED,
DELIVERED,
RETURNED
}
// 当做OrderStatus.PENDING是一个具体的值就好
const myStatus = OrderStatus.PENDING;
// 使用了OrderStatus类型,那我们穿参数的时候就必须要传OrderStatus类型了
function isDelivered(status: OrderStatus) {
return status === OrderStatus.DELIVERED
}
isDelivered(OrderStatus.DELIVERED);
不太好理解的话,我们就可以直接理解为PENDING就是1,相当于 const PENDING = 1;
另外,enums是可以被赋值的。
enum OrderStatus {
PENDING = 666,
SHIPPED = 66,
DELIVERED = "felix",
RETURNED = "666",
}
NOTE: 赋值为string的时候,需要注意后面的也需要被赋值,而不能是默认的值。(默认值为0,1,2,3,4)。
目前感觉就不用赋值,把它当做一个常量来使用就可以了!只是更换了定义常量的方式而已。有个好处是我们确保常量不会被更改。
那么enums到底是啥呢?我们可以看看它被编译成JS是啥样的。
首先编译之后的JS是什么我们先不去理解,但是很明显,编译之后它给JS注入了很多代码。所以很多人不喜 欢用enums。
我们也还可以在enums前面加上const声明,我们看看效果是咋样。
可以看到enums的代码已经不见了,当我们需要使用enums的值是,就会赋一个默认值在上面。
Anyway,enums有些人喜欢使用,有些人不太用,完全取决于自己。enums的好处是定义常量比较简单清晰,同时它能确保常量值不被修改。(但其实我们用const声明基本类型的常量也是不能改的)
Interfaces
Interfaces和正常使用type声明一个类型,基本上是一样的。至于他们两个的区别,后面咱们再解释。interfaces只用来声明object的类型。
- readonly和option的操作符都是和type声明是一样的。
- 对象里面声明方法的方式也是一样的,必须遵守参数,返回值的完全一致。
// readonly和option的操作符都是和type声明是一样的。
type Point1 = {
x: number;
y: number;
}
const p1: Point1 = {
x: 12,
y: 34,
}
interface Point2 {
readonly x: number;
y?: number;
}
const p2: Point2 = {
x: 56,
y: 78,
}
// 对象里面声明方法的方式也是一样的,必须遵守参数,返回值的完全一致。
type Person1 = {
firstName: string;
lastName: string;
nickName?: string;
sayHi: () => string;
}
interface Person2 {
firstName: string;
lastName: string;
nickName?: string;
sayHi: (message: string) => string;
}
const felix: Person1 = {
firstName: 'felix',
lastName: 'luo',
nickName: 'fei',
sayHi: (msg: string) => { // msg不要求和message完全一致,但需要类型一致
return msg;
}
}
const felix2: Person2 = {
firstName: 'felix',
lastName: 'luo',
nickName: 'fei',
sayHi: () => {
return 'hello'!
}
}
interfaces VS types
interface可以往里添加内容,reopen
具体可以看下面的代码,具体使用场景还不晓得。
interface Dog {
name: string;
age: number;
}
// 重新使用关键字,声明同一个interface,那么上下两个Dog他们之间是会合并在一起。
interface Dog {
breed: string;
bark(): string;
}
// 这里的never需要满足上述两个Dog声明的4个属性
const never: Dog = {
name: 'never',
age: 3,
breed: '雪纳瑞',
bark() {
return 'woof!';
}
}
type Cat = {
name: string;
age: number;
}
// 这里再次声明Cat,直接报错
type Cat = {
}
interface可以继承
没错,就是我们所熟知的继承。一个interface可以继承多个interface。
interface Dog {
name: string;
age: number;
}
interface Dog {
breed: string;
bark(): string;
}
interface Animal {
type: stirng;
}
// 这里继承了Dog之后,会把Dog所有的属性继承下来
interface SportDog extends Dog, Animal {
sport: "basketball" | "football" | "baseball";
}
// 这里需要包含所有的属性才不会报错
const never: SportDog = {
name: 'never',
age: 3,
breed: '雪纳瑞',
bark() {
return 'woof!';
},
sport: "baseball",
type: 'Dog',
}
Interfaces 和 types的区别
主要区别:
- Interface只能描述对象,而type可以描述所有类型
- Interface可以reopen,type不行。
- Interface可以继承,type可以使用&符号实现类似功能
Typescript中重要的一些设置。
www.typescriptlang.org/tsconfig
初始化ts文件
tsc --init //生成Typescript的配置文件
监控ts文件的变化,及时生成js文件
tsc --watch fileName or not
tsc -w fileName or not
设置需要进行编译的ts文件
files
Specifies an allowlist of files to include in the program. An error occurs if any of the files can’t be found.
{
"compilerOptions": {},
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
}
include 和 exclude
需要注意的是,如果files设置了值,那么include就会失效,默认就是[]。如果files没有设置值,include的默认值是**/*。
LINK: www.typescriptlang.org/tsconfig#in…
exclude是跳过include里面包含的文件。默认值node_modules, bower_components, jspm_packages, outDir。
LINK: www.typescriptlang.org/tsconfig#ex…
outDir
这个就是编译成js文件时输出的目录。
LINK: www.typescriptlang.org/tsconfig#ou…
target
target是Typescript编译成JavaScript时,允许使用的JavaScript规范。比如说默认值是es5,那么我们在ts中写了箭头函数,编译成js的时候,就会帮你改为function函数。 如果把这个值改为es6或es2015,那么从ts编译成js的时候,箭头函数就不会被改为function函数,因为es6是允许使用箭头函数的
LINK: www.typescriptlang.org/tsconfig#ta…
strict
严格模式。默认是true。如果设置为false,可以单独打开一些设置,比如不允许String类型的值为undefined或null。一般情况下不建议设置为false,我们使用ts最大的目的就是type check。
LINK: www.typescriptlang.org/tsconfig#st…
Working with DOM
built-in type
在Typescript中,DOM对象跟JavaScript一样也是内置的。与JavaScript不同的是,ts中使用DOM对象时,它们的类型早已声明好了。(built-in)
这个类型文件在我们安装Typescript的时候就带上了。我们可以通过配置tsconfig文件来控制是否使用要使用这些内置类型。
配置路径:"compilerOptions" -> "lib" 默认这个配置是被注释了,也就是所有的内置类型文件都是可以用的。在某些特殊情况下,你可能不需要用到所有的内置类型文件,如在node项目中就不需要dom的类型。此时可以通过这个配置来配置你所需要的内置类型文件即可。
LINK: www.typescriptlang.org/tsconfig#li…
Non-null的处理
假设我们此时使用getElementById获取一个元素,Typescript返回的值的类型为HTMLElement | null。因为它不知道这个元素是否存在,所以可能会有null的情况。
我们常用的处理情况应该是使用btn之前,使用链式操作符来判断这个值是否是true(其实跟判空差不多,具体的可以参阅MDN)。
const btn = document.getElementById('btn');
btn?.addEventListener('click', () => {
alert('click me!');
})
另外Typescript也提供了Non-null的符号。(仅仅存在于Typescript中) 如果你能确认返回的值一定不会是null,那么你可以在这个方法执行后面加上叹号(!)
const btn = document.getElementById('btn')!;
btn.addEventListener('click', () => {
alert('click me!');
})
可见此时返回的btn的类型仅仅是HTMLElement,而不会是null。
第二种方式仅仅在你确确实实能保证返回值不会是null时才可以使用。
type assertions
类型断言,是你能确定某个时候某个值的类型是你能确认的,可以让Typescript暂时认为你说的这个类型是正确的,不会出现类型判断错误。用一句话来说 I know you more than you. 具体看例子即可明白。
如图所示,something被我们设置为unknown类型。此时我想获取something的长度,因为我能确保它是一个字符串。所以我们可以直接使用as关键字,将something假设成string,此时在去获取字符串的长度,就不会报错了。如果直接使用something.length,则会报错,因为unknown没有length属性。
在dom的操作中,assertions中会有实际的用处,具体我们看例子。 HTML页面中有一个button和一个input,我们点击button的时候输出input的值。
const btn = document.getElementById('btn')!;
const input = document.getElementById('todoinput')!;
btn.addEventListener('click', () => {
alert(input.value)
;})
但此时会报错。
因为我们获取元素的时候,typescript并不知道我们获取的元素是哪种类型,比如HTMLButtonElement,HTMLInputElement...所以返回的元素的类型默认是HTMLELement | null。 但是HTMLElement上是没有Value这个属性的。所以会报错。
我们自己能够确定获取到的是HTMLInputElement,所以我们此时就可以使用assertions,来获取这个value。
const btn = document.getElementById('btn')! as HTMLButtonElement;
const input = document.getElementById('todoinput') as HTMLInputElement;
btn.addEventListener('click', () => {
alert(input.value)
;})
此时就不会报错了。
其实我们可以理解assertions为帮助Typescript来识别我们的类型,有时候他并不够智能,我们需要提供帮助,因为我们有时候确实比它更了解它。