官方学习文档
Handbook - The TypeScript Handbook
布尔也就两种, true
或者 false
let isBoolean: boolean = false;
function tell() {
alert(isBoolean)
}
tell()
数值类型有很不止 number
, bigint
也是。同时值的话可以是十进制,二进制,还可以是NaN。
let num: number = 10;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
let nan: number = NaN;
function tell() {
num--;
console.log(num)
}
tell()
字符串,可以是普通字符串,还可以是模板字符串
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
let arr:number[] = [1, 2, 3];
// 等同于
let arr:Array<number> = [1, 2, 3];
元组看成是数组的升级版,可以先定义数组里面的每一项的数据类型,在赋值。
let x: [string, number];
x = ['踏浪', 18]; // OK
x = [18, '踏浪']; // Error
要求每一项的数据类型都和定义的类型一致,否则就会报错
同时,也不能在 x
中添加第三项,因为没有定义
Tuple type '[string, number]' of length '2' has no element at index '3'.
7 x[3] = 100;
枚举类型将值限定在一定的范围内,比如一周只有7天,一年只有12个月
enum Color {Red, Green, Yellow, Blue};
enum Color2 {Red = 10, Green, Yellow = 30, Blue = 20};
let colorName: string = Color[1]; // 类似数组获取下标
let color2Name: string = Color2[11]; // 自定义下标顺序
function tell() {
console.log(colorName); // Green
console.log(color2Name); // Green
console.log(Color2[30]); // Yellow 没有固定顺序的定义
console.log(Color2[20]); // Blue 没有固定顺序的定义
}
tell();
let a: any = 10;
a = "踏浪";
a = true;
a = function() {};
a = [1, 2, 3]
定义 any
类型后,就有点像是普通的js了,可以任意修改值类型
function tell(): number {
return 18;
}
function say(): string {
return '踏浪'
}
function other(): void {
return 100; //Error, Type 'number' is not assignable to type 'void'.
}
使用了 void
就不要有返回值了
唯一的 null
和 undefined
let u: undefined = undefined;
let n: null = null;
Object是代表非基本类型的类型,即不是数字,字符串,布尔值,bigint,symbol,null或者undefined的任何类型。
declare function create(o: object): void;
create({name: '踏浪'});
create([1, 2, 3]);
create(10); // Argument of type 'number' is not assignable to parameter of type 'object'.
除了上述的这些类型,基本类型还有 Unknown
Never
等,这些可以去上面提到的官网查看。
在TS中函数也有具名函数与匿名函数之分,只是相对TS中,函数的参数以及返回值都可以指定参数类型
function add(x: number, y: number): number {
return x + y;
}
上面的代码参数与函数返回值的类型都是 number
如果是匿名函数
let sayName = function(x: string, y: number): string {
return `我是${x}, ${y}岁`;
}
上面的函数如果不看返回值,要想知道 x
与 y
的意义是很难的,所以,针对于匿名函数,还有一种方式,可以指定参数的具体的意义
let sayName: (name: string, age: number) => string = function(x: string, y: number): string {
return `我是${x}, ${y}岁`;
}
前面的 (name: string, age: number)
指的就是函数的参数意义, x
表示 name
, y
表示 age
,后面的 =>string
则是指定函数的返回值为 string
类型的。
虽然
function sayName(firstName: string, lastName: string): string {
return `${firstName} ${lastName}`
}
sayName('踏', '浪');
上面的函数接收两个参数,在TS中如果多传递或者少传递参数编译都不会通过,必须传递两个。但是如果我只传递一个参数呢?TS中有一个可选参数,用 ?:
表示
function sayName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName
}
sayName('踏', '浪');
sayName('踏');
还可以设置默认值,有点像是JS中的默认值
function sayName(firstName: string, lastName = "浪"): string {
return lastName ? `${firstName} ${lastName}` : firstName
}
sayName('踏'); // 踏 浪
sayName('踏', '❄️'); // 踏 ❄️
与JS中的 ...
一样,只是可以指定类型
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
基本写法
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
return `${this.name}: ${this.age}`;
}
}
let person = new Person();
上面的代码会有错误 error TS2554: Expected 2 arguments, but got 0.
因为上面我们使用了 constructor
,所以在创建实例的时候必须传递两个参数才可以。
let person = new Person('踏浪', 18);
注意参数类型需要与你的类的 constructor
参数的类型相同。
继承是用 extends
关键字
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
return `${this.name}: ${this.age}`;
}
}
class Student extends Person {
school: string;
constructor(school: string) {
this.school = school;
}
tell() {
return `${this.say()} ${this.school}`
}
}
let person = new Person('踏浪', 18);
上面的 Student
类就是继承于父类 Person
的。但是如果你使用带有提示的编辑器,就会发现有错。错误如下:
错误中提示我们必须要有一个 super
。那么super接收什么参数呢。接收父类 constructor
需要的参数,这一点与ES6中的class继承一节的内容相似。可以看看
如果子类不调用super方法,就无法得到this对象。
...
constructor(school: string) {
super('踏浪', 18);
this.school = school;
}
...
这样就行了。
super
如果是在子类的 constructor
中调用,那么相当于是调用父类的 constructor
方法。如果是在其他非constructor
的地方,就表示一个指向父级的对象,相当于 this
。比如上面代码中的 this.say()
就可以写成 super.say()
。
tell() {
return `${super.say()} ${this.school}`
}
访问修饰符有两个 public(默认)
与 private
。
class Person {
public name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
private say() {
return `${this.name}: ${this.age}`;
}
}
class Student extends Person {
school: string;
constructor(school: string) {
super('踏浪', 18);
this.school = school;
}
tell() {
return `${super.say()} ${this.school}`
}
}
let person = new Person('踏浪', 18);
let student = new Student('北大');
console.log(student.tell());
上面的代码编译会出错。我们在父类的 name
age
和 say
属性与方法前面添加了访问修饰符,子类调用 super.say()
方法出错 Property 'say' is private and only accessible within class 'Person'.
所以,属性或者方法前面添加private
后子类无法访问,不影响父类自己使用,如果父类 say
方法中的 this.age
。
getter
setter
访问,修改私有属性class Hello {
private _name: string;
say() {
return `I am ${this._name}`;
}
get name() {
return this._name;
}
set name(newName: string) {
this._name = newName;
}
}
let hello = new Hello();
hello.name = "踏浪";
console.log(hello.say())
static
也是添加在属性或者方法前面的,如下
class Hello {
static name: string = "踏浪";
say() {
return `I am ${this.name}`;
}
}
上面一段代码会出错
name
是静态成员,不同通过 [this.name](http://this.name)
访问,需要使用类调用
class Hello {
static name: string = "踏浪";
say() {
return `I am ${Hello.name}`;
}
}
先来看一段代码
function sayPersonName(person: {name: string}) {
console.log(person.name);
}
let person = { name: '踏浪', age: 18 };
sayPersonName(person);
上面代码中 sayPersonName
的参数接收一个对象,对象的 name
属性是 string
类型的。这只是一个简单的例子,但是如果对象有很多的属性,每个属性都有各自的类型指定。那么按照上面的写法写出来的代码看着就不舒服,所以,有了接口的概念。
使用 interface
关键字来定义一个接口
interface person {
name: string;
age: number;
ID: number;
}
function sayPersonName(person: person) {
console.log(`I am ${person.name}, ${person.age} years old, ID is ${person.ID}`);
}
let person = {
name: 'Talang',
age: 18,
ID: 10000
};
sayPersonName(person);
上面就是使用接口的例子,例子中有多个 person
,希望你能明报每个 person
所代表的含义。
第一个 interface
的 person
是定义的一个 person
接口,指定一个对象以及对象每个属性的类型。
第二个是 sayPersonName
中的两个 person
,第一个是指函数接收的形式参数,第二个是形式参数的类型,就是上面使用 interface
定义个接口类型。
第三个是 let
声明的 person
变量。
第四个是函数sayPersonName
调用传递的 let
定义的这个对象。
这就是接口的最基本的使用。定义的接口的属性,在传递参数的时候需要全部都要确定。如果你试过声明 person
的时候不传递某个属性,就会出错。那么有什么方法改善呢。这就需要用到可选属性了。
interface person {
name: string;
age: number;
ID: number;
}
function sayPersonName(person: person) {
console.log(`I am ${person.name}, ${person.age} years old`);
}
let person = {
name: 'Talang',
age: 18,
// ID: 10000
};
sayPersonName(person);
上面的代码编译的时候会出错 Property 'ID' is missing in type '{ name: string; age: number; }' but required in type 'person'.
前面讲函数参数的时候涉及到可选参数,使用 ?:
的形式表示的,其实,这里也是一样的。
interface person {
name: string;
age: number;
ID?: number;
}
...
这样定义就不会出错了
interface SearchFunction {
(keyword: string, page: number): string;
}
let mySearch: SearchFunction = function(keyword: string, page: number) {
return page > 10 ? keyword : 'false';
}
(keyword: string, page: number): string;
就是定义的函数的类型,参数以及返回值。
interface stringArray {
[index: number]: string
}
let arr: stringArray = ['10', '10'];
数组的每一项都要是 string
类型的。
声明类的时候要使用定义的接口需要使用 implements
关键字,声明的类中的属性需要有接口中的参数
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
constructor(h: number, m: number) {}
}
接口也是可以继承的
interface Color {
color: string;
}
interface Shape {
shape: string;
}
interface Pos {
x: number;
y: number;
}
interface Box extends Color, Shape { // 继承多个使用 , 隔开
position: Pos;
size: number;
}
let position: Pos = {
x: 100,
y: 100
}
let box: Box;
box = {
color: "red",
shape: "circle",
position: position,
size: 100
};
function Hello(num: number): number {
return num;
}
上面定义了一个函数 Hello
。参数与返回值都是 number
。但是如果我要修改为 string
或者其他类型。那就需要重新写一个函数。
你或许认为可以使用 any
类型,但是如果使用 any
那么就失去了类型规范与校验的功能。所以这时候就需要用到泛型了。
function Hello<T>(arg: T):T {
return arg;
}
泛型的写法 <T>
中间的大写字母为 T
是约定俗成的。
上面的函数在声明的时候使用了泛型,在使用的时候在指定具体的类型。
let output = Hello<string>('踏浪');
<>
中的类型与参数的类型需要一致,因为声明的函数类型是一样的。当然,在定义函数的时候,参数、返回值也可以是其他的类型。
function Hello<T>(arg: T):T {
return arg;
}
let myHello: <T>(arg: T) => T = Hello;
console.log(myHello<string>("Hello"));
let myHello2: {<T>(arg: T):T} = Hello;
console.log(myHello2<string>("Hello2"));
let myHello3 = Hello;
console.log(myHello3(10))
上面三种写法都可以,但是前两种会对新的函数做出类型检查,但是第三个函数确实不会的,直接传递也能编译成功,只是相当于执行了Hello函数而不做类型检查。
上面的前两个声明,我们可以定义一个接口
interface Hello<T> {
(arg: T): T;
}
或者这样写也是可以的
interface Hello {
<T>(arg: T): T;
}
class Hello<T> {
name: T;
say: (name: T) => T;
constructor(name: T, say: (name: T) => T) {
this.name = name;
this.say = say;
}
}
let hello = new Hello<string>("踏浪", () => "hello");
TS中的模块与ES6中的模块差不多,可以看
需要关注的一点就是在TS中的通配符声明
declare module '*.gif' {
const content: any
export default content
}
declare module '*.png' {
const content: any
export default content
}
declare module '*.svg' {
const content: any
export default content
}
declare module '*.ttf' {
const content: any
export default content
}
这种声明允许在 TS 中 import 非 JavaScript 文件。
命名空间就像是声明一个对象,对象中还有其他属性,可以通过 .
操作符获取属性
declare namespace Person {
export interface Name {
name: string;
}
export interface Age {
age: number;
}
export class Color {
color: string;
}
interface Address {
address: string;
}
interface Id {
id: number;
}
}
使用的话可以直接 [Person.Name](http://person.Name)
来使用。在项目中,放在 .d.ts
文件中默认全局。如果没有使用 declare
关键字,需要手动 export
。
装饰器就是在不调用函数的情况下执行函数。装饰器只能装饰类以及类的方法,属性
function hello() {
console.log('hello');
}
@hello
class Hello {}
上面的 @hello
就是一个装饰器,放在 class Hello
上面,是对 Hello 类的装饰。你还可以对属性,方法进行装饰,只需要放在那一行的上面即可。如果是一行,放在这一行的前面即可。
类的装饰,装饰器只能接收一个参数,参数指向这个类
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
上面的装饰器为 MyTestableClass
类添加了一个静态属性 isTestable
,且值固定为 true
。那我可以手动的传递参数么?可以的。
@testable(true)
class MyTestableClass {
// ...
}
function testable(isTestable) {
return function (target) {
target.isTestable = isTestable
};
}
MyTestableClass.isTestable // true
方法的装饰接收三个参数。装饰器第一个参数是类的原型对象,第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
function readonly(target, propertyKey, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
对于类的 getter
与 setter
也是可以进行装饰的,使用方法与类的方法的装饰一样
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
属性的装饰一般很少用到。可以看看
ES6中也有装饰器的概念
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
}
// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
// 因为用implements,需要在子类里实现所有接口定义。非常繁琐
class SmartObject implements Disposable, Activatable {
constructor() {
setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
}
interact() {
this.activate();
}
// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);
let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);
////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
关键点在于 implements
的 SmartObject
中的部分属性只是占位没有具体实现,而是通过 Mixins
而混合而成。相当于一直继承的简写。
以上就是关于TS中常用到的,部分例子来自于官网,这里只是学习总结,以加深印象,方便查阅。