@yacent
2017-12-04T00:05:35.000000Z
字数 14046
阅读 1297
TypeScript
网址: ts.xcatliu.com
TypeScript是JavaScript的一个超集,主要提供了 类型系统 和 对ES6的支持
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
TypeScript的好处
.js
文件可以直接重命名为 .ts
即可但是TypeScript也有自己的缺点
简单的例子
function sayHello(person: string) {
return 'Hello' + person;
}
let user = 'Tom';
console.log(sayHello(user));
然后执行
tsc hello.ts
这时候会生成一个编译好的 hello.js
function sayHello(person) {
return 'Hello' + person;
}
var user = 'Tom';
console.log(sayHello(user));
TypeScript中,使用 :
指定变量的类型;
如果上面的代码当中,user的类型不是 string,ts会报错,但是还是会编译出一个 hello.js
。
TypeScript只会进行静态检查, 如果发现有错误,编译的时候就会报错。
如果想要在报错的时候就终止js文件的生成,可以在 tsconfig.json
当中配置 noEmitOnError
在 JavaScript当中,有五种基本数据类型
boolean
、number
、string
、undefined
、null
以及 ES6的 Symbol
let isDone: boolean = false
注意: new Boolean(1) 其返回的类型不是 boolean
,而是 object
,如果是直接调用 Boolean(1)
,那么其返回的就是 boolean
类型
在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样,不再赘述。
使用 number
定义数值类型
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
使用 string
定义字符串类型:
let myName: string = 'Tom'
JavaScript没有空值(void) 的概念,在TypeScript中,可以用 void
表示没有任何返回值的函数
function alertName(): void {
console.log(111);
}
变量声明为 void
并没有什么用,只能将它赋值为 undefined
或者 null
在TypeScript中,可以使用 null
和 undefined
来定义这两个原始数据类型
let u: undefined = undefined;
let n: null = null;
undefined
类型的变量只能被赋值为 undefined
,null
类型的变亮只能被赋值为 null
。
与
void
区别在于,undefined
和null
是所有类型的子类型,即undefined
类型的变量,可以赋值给number
类型的变量
let num: number = undefined;
// 通过
let u: void;
let num1: number = u;
// 编译错误
任意值(Any)用来表示允许赋值为任意类型
相比于普通类型,普通类型在赋值的过程中改变类型是不允许的
let m: string = 'seven';
m = 7;
// index.ts(2, 1): error TS2322: Type 'number' is not assignable to type 'string'
但如果是 any
类型,则允许赋值的时候改变类型
let m: any = 'seven';
m = 7;
任意值:
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
在ts当中,如果变亮在声明的时候,未指定其类型,那么它会被识别为任意类型(any),与原来的 js 一样。
let s; // 注意这里并没有给他赋值,所以会被推断为 any类型
s = 'seven';
s = 7;
如果没有明确的指定类型,那么TypeScript会依照类型推论(Type Inference)的规则推断出一个类型。
let m = 'seven';
m = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
实际上等价于
let m: string = 'seven';
m = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript会在声明并且赋值的情况下,如果没有迷宫却的指定类型,则会推测出一个类型,这就是类型推论
如果定义的时候没有赋值,则会被推断为 any
类型
联合类型(Union Types)表示取值可以为多种类型中的一种
let m: string | number;
m = 'seven';
m = 7;
m = true;
// Type 'boolean' is not assignable to type 'string | number'
联合类型使用 |
分割每个类型
当TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问共有的属性或方法
function getLength(s: string | number):number {
return s.length
}
// error TS2339: Property 'length' does not exist on type 'string | number'.
因为 length
不是 string 和 number 的共同属性,所以会报错。
在TypeScript中,我们使用接口(Interfaces)来定义对象的类型
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。(即描述对象的格式)
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
}
接口字母一般首字母大写,并且,赋值的时候,变量的形状必须和接口的形状保持一致
可选属性
有时候,我们希望不要完全匹配一个形状,那么可以使用可选属性。但是,这是仍然不允许添加未定义的属性。
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
任意属性
即允许接口当中,可以任意添加未曾定义过的属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}
使用 [propName: string]
定义了任意属性取 string
类型的值
tips: 一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
只读属性
有时候希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性
interface Person {
readonly id: number;
name: string;
}
let tom: Person = {
id: 89757,
name: 'Tom
}
tom.id = 123;
// error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
在TypeScript中,数组类型有多种定义方式,比较灵活
[类型 + 方括号] 表示法 最常用
其中,类型可以是之前提到过的所有数据类型,string、number、boolean、any、interface、联合类型等
let fibo: number[] = [1, 1, 2, 3, 5]
let union: (number | string)[] = [1, '1']
数组中的项 不允许 出现其他的类型
数组泛型
也可以使用数组泛型(Array Generic) Array<elemType>
来表示数组
let fibo: Array<number> = [1, 1, 2, 3, 5]
接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibo: NumberArray = [1, 1, 2, 3, 5];
NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number。
类数组
类数组(Array-like Object)不是数组类型,即伪数组,不是数组类型,实际上,类数组有自己的接口定义,如 IArguments
NodeList
HTMLCollection
function sum() {
let args: IArguments = arguments;
}
函数是有输入和输出的,需要同时考虑到输入和输出的类型
函数声明的类型定义
function sum(x: number, y: number): number {
return x + y;
}
输入过多或者过少的参数都是不被允许的,一般情况下,其实比较常用的是限制函数的参数的数据类型,而对于其返回的数据类型,通常是不会做限制的。
函数表达式的类型定义 不常用
let mySum: (x: number, y: number) => number = (x: number, y: number):number => {
return x + y;
}
注意两个点:
mySum
的数据类型=>
和 右边的 ES6的 =>
是不同的,ts的表示函数的定义,而es6中的叫 箭头函数通接口类型当中的一样,可以设定 可选参数,但是需要注意,可选参数必须放在必选参数的后面。
function mySum(x: number, y?: number): number {
// ...
}
ES6当中,允许我们在参数当中传入默认值
类型断言(Type Assertion)可以用来手动指定一个值的类型
<类型>值
或 值 as 类型
在tsx语法当中,必须使用后一种
类型断言的使用之处,比如在联合类型当中,使用属性或者方法的时候,必须使用联合类型当中共有的属性或方法
function getLength(m: string | number) {
if (m.length) {
return m.length;
}
// ...
}
// 报错
因为 number没有 length
属性,但我们可以使用类型断言,将m 断言成 string
function getLength(m: string | number) {
if ((<string>m).length) {
return (<string>m).length;
}
}
// tsx 下必须写成 as 的形式
function getLength(m: string | number) {
if ((m as string).length) {
return (m as string).length;
}
}
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
当然,你也可以使用 typeof
、instanceof
等 来进行类型的判断
function getLength(m: string | number) {
if (typeof m === 'string') {
return m.length;
}
// ...
}
有时候使用第三方库的时候,比如jq,需要声明 $ 是什么鬼东西
使用 declare
关键字
declare var $: (string) => any;
$('foo')
declare 定义的类型只会用于编译时进行检查,在编译结果中会被删除。
详情请看文档
通常来说,js当中有的内置对象,如 Math,Date等等,typeScript都已经是封装好了
使用 type
创建类型别名,通过类型别名用来给一个类型起个新的名字,常用语联合类型当中,在平常的使用中,使用频率比较低。
此外,type
还是用来定义字符串字面量类型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
let m: NameOrResolver;
字符串字面量类型用来约束取值只能是某几个字符串中的一个
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: Eventnames) {
// do something
}
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象
let xcatliu: [string, number] = ['Xcat Liu', 25]
如果是声明的时候就定义元组,那么必须把元组当中的类型都对齐,不然会报错,如果不是声明的时候定义,那么就可以一个个进行赋值
let xcatliu: [string, number];
xcatliu[0] = 'Xcat Liu';
xcatliu[1] = 25;
越界的元素
当赋值给越界的元素时,它类型会被限制为元组中每个类型的 联合类型
let xcatliu: [string, number] = ['Xcat Liu', 25, 'http://']
上面的第三项,只需要满足 string | number
类型即可
枚举使用 enum
关键字来进行定义
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
使用 enum
实际上会进行一个映射
console.log(Days["Sun"] === 0); // true
因为其编译过程的结果如下,自己加了一个双向映射
var Days;
(function(Days) {
Days[Days["Sun"] = 0] = "Sun"
})
也可以手动进行赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}
未手动赋值的枚举项,会接着上一个枚举项进行递增,递增步长为 1
其他和ES6都是相同的,要注意一点,是 静态方法
class Animal {
static isAnimal(a) {
return a instanceof Animal
}
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
使用 static
修饰符修饰的方法称为 静态方法,它们不需要实例化,而是通过类来调用
实例属性
ES6中实例的属性只能通过构造函数中的 this.xxx
来定义,而ES7当中,可以直接在类里面定义
class Animal {
name: 'Jack';
constructor() {
// ...
}
}
TypeScript可以使用三种访问修饰符(Access Modifiers),分别是 public
private
和 protect
public
修饰的属性或方法是公有的,可以在任何地方被访问到,如果不加,默认所有的属性和方法都是 public
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和 private
类似,区别是它在子类中也是允许被访问的关键字 abstract
用于定义抽象类和其中的抽象方法
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack'); // error TS2511: Cannot create an instance of the abstract class 'Animal'.
class Cat extends Animal {
public sayHi() {
console.log(`Meow. My name is ${this.name}`)
}
}
给类加上TypeScript的类型很简单,与接口类似
class Animal {
name: string,
constructor(name: string) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
之前学习过,接口(Interfaces)可以用于对[对象的形状(Shaped)]进行描述
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements
关键字来实现。这
个特性大大提高了面向对象的灵活性。
interface Alarm {
alert(); // 此处不是具体的实现,而是一个声明或者语句,具体的实现需要到类当中去实现该方法所展示的东西
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert() {
// do something
}
}
class Car implements Alarm {
alert() {
// do something
}
}
一个类可以实现多个接口
interface Alarm {
alert();
}
interface Light {
lightOn();
}
class Car implements Alarm, Light {
alert() {}
lightOn() {}
}
interface Alarm extends MoreAlart {
// something
}
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
上面可以实现返回初始化值的数组,但是有一个问题,其不能规定返回的数据的类型,即传入的变量的类型最终有可能和输出的数组的类型不同。
这个时候,泛型的用处就在这,可以规定返回数组的类型
function createArray<T>(length: number, value: T): Array<T> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
or
createArray(3, 'x'); // ['x', 'x', 'x']
在函数名后家加了 <T>
,其中 T
用来只带任意输入的类型
接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来。
functoin swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 's']); // ['s', 7]
我们可以对泛型进行约束,只允许这个函数传入包含某些东西的变量,因为T可以是任何类型的,所以如果不加约束而直接使用一些并不公用的属性或方法,就会报错
interface Lengthwise {
length: number;
}
function log<T extends Lengthwise>(arg: T): T {
return arg.length
}
多个参数类型之间也可以互相约束
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
使用了两个类型参数,T 继承 U,这就保证了 U上面不会出现 T 不存在的字段
class GenericNumber<T> {
zeroValue: T;
add (x: T, y: T) => T
}
let my = new GenericNumber<number>();
my.zeroValue = 0;
my.add = function(x, y) {return x + y;};
去 中文网看一下 JSX、工程配置相关的东西
TypeScript具有三种JSX模式:preserve
、react
、react-native
可以通过命令行里使用 --jsx
标记或者 tsconfig.json
里的选项来指定模式
{
"files": [
"index.d.ts",
"test/index.ts",
"test/tsx.tsx",
"test/cssProperties.tsx"
],
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"noImplicitThis": false,
"strictNullChecks": true,
"strictFunctionTypes": false,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"jsx": "preserve"
}
}
对于类(组件),可以指定自定义组件当中的props的类型以及state当中的类型
interface PropsType {
children: JSX.Element
name: string
}
inteface StateType {
name: string;
age: number;
}
class Component extends React.Component<PropsType, StateType> {
render() {
return (
<h2>
this.props.children
</h2>
)
}
}
// OK
<Component>
<h1>Hello World</h1>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element
<Component>
<h1>Hello World</h1>
<h2>Hello World</h2>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element or string.
<Component>
<h1>Hello</h1>
World
</Component>
通过 export 和 import 来进行模块的管理
定义一个外部模块,通常外部模块都是通过 .d.ts
文件进行声明,这时候的模块类似于 Npm当中的包模块
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQuerystring?, s?): Url
}
/// <reference path="node.d.ts">
import * as URL from "url"
let myUrl = URL.parse("http://www.typescriptlang.org");
内部模块称之为 “命名空间”,外部模块简称为 “模块”
“内部模块”现在叫做“命名空间”。 另外,任何使用
module
关键字来声明一个内部模块的地方都应该使用namespace
关键字来替换。 这就避免了让新的使用者被相似的名称所迷惑。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
}
项每一个JS文件对应一个模块一样,TypeScript当中的模块文件与生成的JS文件也是一一对应的。所以对于模块的声明,能合并就合并,不要生成过多的JS文件,导致加载问题。
相对导入是以 /
、./
或../
等开头的
如
import Entry from "./component/Entry"
而其他形式的导入,都被当作为非相对的
import * as $ from "jQuery"
共有两种可用的模块解析策略: Node
和 Classic
这种策略以前是 TypeScript默认的解析策略。现在,主要是为了向下兼容,其搜索模块的过程是 先 ts
再 tsx
,然后向涟漪,由里到外
其解析过程如下,假设下面的 import
的位置都发生在文件 /root/src/A.ts
那么,对于相对模块,import {b} from './moduleB'
其过程如下
1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
对于 非相对模块,import {b} from 'moduleB'
,其过程如下
1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/moduleB.ts
4. /root/moduleB.tsx
5. /moduleB.ts
6. /moduleB.tsx
这个解析策略试图在运行时模仿 Node.js 模块解析机制,即 先 js
然后 再 package.json
,最后才是 index.js
同样,如下模块引用场景都是在 文件路径为 /root/src/moduleA.js
当中发生
对于 相对模块,var x = require("./moduleB")
,其解析过程如下
1. 将 /root/src/moduleB.js 视为文件,检查其是否存在
2. 将 /root/src/moduleB 视为目录,检查是否它包含 package.json文件,并且指定了一个 main模块,在例子中,如果发现了 /root/src/moduleB/package.json 包含了 { "main": "lib/mainModule.js" },那么Node.js 会引用 /root/src/moudleB/lib/mainModule.js
3. 将 /root/src/moduleB 视为目录,如果没有 package.json文件,则检查它是否包含 index.js 文件。这个文件会被隐藏式地当作那个文件下的 main 模块
4. 如果还找不到,就会报错了
在业务代码中,可以看 引入 allActions 的过程,就很好的解释了 相对模块的解析过程
但是,非相对模块 的解析是个完全不同的过程。Node会在一个特殊的文件夹 node_modules
里查找你的模块,node_modules
可能与当前的文件在同一目录下,或者上层目录里,Node会向上级目录遍历,查找每个 node_modules
直到它找到要加载的模块
同样,如果 var x = require("moduleB")
,Node会以下面的顺序去解析 moduleB
,直到(即会一直匹配)有一个匹配上,其实与 相对模块 的过程差不多,只是在 node_module
下进行
1. /root/src/node_module/moduleB.js
2. /root/src/node_module/moduleB/package.json
3. /root/src/node_module/moduleB/index.js
4. /root/node_module/moduleB.js
5. /root/node_module/moduleB/package.json
6. /root/node_module/moduleB/index.js
7. /node_module/moduleB.js
8. /node_module/moduleB/package.json
9. /node_module/moduleB/index.js
TypeScript 模仿 Node的模块实现机制,在此基础上,增加了 TypeScript源文件的扩展名(.ts
,.tsx
和 .d.ts
),同时,TypeScript在 package.json
里使用字段 types
来表示类似 main
的意义
如果,有一个导入语句 import {b} from './moduleB'
,在 /root/src/moduleA.ts
里,会以下面的流程来定位 "./moduleB"
1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/src/moduleB.d.ts
4. /root/src/moduleB/package.json
5. /root/src/moduleB/index.ts
6. /root/src/moduleB/index.tsx
7. /root/src/moduleB.index.d.ts
https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react/test
去学习,还有工程配置的东西`