复习TypeScript-基础

1.关于基础类型(重点说 any、object等)

any

刚开始的问题是 any 用的比较多,但是事实上,type-check 本来就是为了确定类型,写那么多不能确定类型的 any 干什么

适用于any的情况:

当我们确实不知道变量的类型的时候,需要取消类型检查。例如下面的情况(These values may come from dynamic content)

  • from the user
  • from a 3rd party library
  • you know part of the type,but not all of it (Array)
1
2
3
let list: any[] = [1, true, "free"];

list[1] = 100;

any是一个非常有用的方式,对于 引入 还是取消类型检查。

与Object不同—调用方法

1
2
3
4
5
6
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
object

非基础类型 使用object,i.e.

any thing that is not number, string, boolean, symbol, null, or undefined.

1
2
3
4
5
6
declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
Type assertions—

相当于强制类型转换

自己了解到的类型的信息比TS要多,你知道的额尸体类型的信息比现在要更具体的时候

两种方式写 Type assertions

  • value
  • value as type

举例:

1
2
3
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
1
2
3
let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

2.interfaces(接口)

变量

注意点:

  • 实际传入的可以比接口中定义的类型因为只是检查必须的
1
2
3
4
5
6
7
8
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

只是检查必须的

  • 接口中的可选属性
  • readonly 只能在声明时赋值,但是可以使用断言的方式
1
2
3
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
a = ro as number[];
    • readonly与const

    变量使用const

    属性使用readonly

  • 超额检查

1
2
3
4
5
6
7
8
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 });

你可能会认为此程序输入正确,因为width属性是兼容的,没有color属性存在,并且额外的colour属性是无关紧要的。

但是,TypeScript认为此代码中可能存在错误。Object literals 在将它们分配给其他变量或将它们作为参数传递时,会得到特殊处理并进行多余的属性检查

如果Object literals具有“目标类型”没有的任何属性,则会出现错误。

1
2
// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });

colour 是多余的,target type 中是没有的

解决这个问题:

  1. 断言
1
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
index signature(索引签名)—

如果您确定该对象可以具有某些特殊方式使用的额外属性

如果SquareConfig能有colorwidth上述类型的属性,但可能有任意数量的其他性质的,那么我们就可以像这样定义它:

1
2
3
4
5
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
  1. 赋值给一个变量
1
2
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

Since squareOptions won’t undergo excess property checks, the compiler won’t give you an error.

Keep in mind that for simple code like above, you probably shouldn’t be trying to “get around” these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. That means if you’re running into excess property checking problems for something like option bags, you might need to revise some of your type declarations. In this instance, if it’s okay to pass an object with both a color or colour property to createSquare, you should fix up the definition of SquareConfig to reflect that.

接口中声明函数
1
2
3
interface SearchFunc {
(source: string, subString: string): boolean;
}
1
2
3
4
5
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}

参数名可以不用匹配,下面这样也是可以的

1
2
3
4
5
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
Indexable Types(可转换类型)

Indexable types have an index signature that describes the types we can use to index into the object, along with the corresponding return types when indexing

1
2
3
4
5
6
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["BOb","Fred"]
let myStr:string = myArray[0]

Above, we have a StringArray interface that has an index signature. This index signature states that when a StringArray is indexed with a number, it will return a string.

There are two types of supported index signatures: string and number. It is possible to support both types of indexers, but the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. This is because when indexing with a number, JavaScript will actually convert that to a string before indexing into an object. That means that indexing with 100 (a number) is the same thing as indexing with "100" (a string), so the two need to be consistent.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}

// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
// 意思是这里的Animal与Dog应该换一下?
}

While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name’s type does not match the string index’s type, and the type-checker gives an error:

1
2
3
4
5
6
interface NumberDictionary {
[index: string]: number;
length: number; // ok, length is a number
name: string; // error, the type of 'name' is not a subtype of the indexer
//这里是说 name: number
}

Finally, you can make index signatures readonly in order to prevent assignment to their indices:

1
2
3
4
5
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

can’t set myArray[2] because the index signature is readonly

疑问:签名索引和这个可转换类型 相似

1
2
3
4
5
6
7
8
9
10
11
12
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}

class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}

Interfaces describe the public side of the class, rather than both the public and private side. This prohibits you from using them to check that a class also has particular types for the private side of the class instance.

混合类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

与第三方JavaScript交互时,可能需要使用上述模式来完整描述类型的形状

3.类

传统的javascript构建可重用的组件是使用函数基于原型的继承

类的写法形式:

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");

复杂的继承的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}

class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m

tom 仍然使用的是Horse重写的move方法

修饰符

public(默认)、private、protected

private

TypeScript是一种结构类型系统。当我们比较两种不同的类型时,无论它们来自何处,如果所有成员的类型都是兼容的,那么我们说类型本身是兼容的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
constructor() { super("Rhino"); }
}

class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // Error: 'Animal' and 'Employee' are not compatible

However, when comparing types that have private and protected members, we treat these types differently. For two types to be considered compatible, if one of them has a private member, then the other must have a privatemember that originated in the same declaration. The same applies to protected members.

我的理解:

虽然都是 Private 声明的同名变量,但一个是这个一个是那个,就像有两个具有相同属性的对象,还是不一样的,所以解释中说,必须来自同一个源,

protected

是可以在子类中访问,但是也是不可以在子类的外面访问的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}

class Employee extends Person {
private department: string;

constructor(name: string, department: string) {
super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error

protected标记构造函数,这意味着该类不能在其包含的类之外实例化,但可以扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}

// Employee can extend Person
class Employee extends Person {
private department: string;

constructor(name: string, department: string) {
super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

使用 protected 标记的构造函数,在类的外面是不能访问的,但是可以在子类中访问

  • 构造子类
  • 子类构造方法中 super 调用父类的constructor

但是是不可以直接在外面使用 new 的方法构造一个对象的。

又出现的readonly

1
2
3
4
5
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}