TypeScript学习之二——基础

TypeScript学习之二——基础

  • 原始数据类型
  • 任意值
  • 类型推论
  • 联合类型
  • 对象的类型——接口
  • 数组的类型
  • 函数的类型
  • 类型断言
  • 声明文件
  • 内置对象

1. 原始数据类型

JAvaScript数据类型分为两种:原始数据类型和对象类型
原始数据类型包括:String、Number、Boolean、null、undefined、Symbol

1.1 布尔值
let isDone:boolean = false;

注意:使用构造函数创建的是对象类型不是布尔类型

let isDone:boolean = new Boolean(1);

// Type 'Boolean' is not assignable to type 'boolean'.
// 'boolean' is a primitive, but 'Boolean' is a wrapper object.
// Prefer using 'boolean' when possible.

let s:Boolean = new Boolean(1);
//这种写法就是正确得了,new Boolean()返回的是一个Boolean对象

直接调用Boolean也可以返回一个boolean类型。

let s:boolean = Boolean(1);

1.2 数值
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

编译结果:

var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;

1.3 字符串
let myName: string = 'Tom';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;

编译结果:

var myName = 'Tom';
var myAge = 25;
// 模板字符串
var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";

1.4 空值

JavaScript中没有空值的概念,在TypeScript中,可以用void表示没有任何返回值的函数:

function alertName():void{
	alert('alert');
}

//声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable:void = undefined;
let unusable:void = null;

1.5 Null和Undefined
let u:undefined = undefined;
let n:null = null;

与void的区别是,undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量。

let num:number = undefined;

2. 任意值

任意值(Any)用来表示允许赋值为任意类型

2.1 什么是任意类型

如果是一个普通类型,在赋值过成中改变类型是不被允许的

let myFavoriteNumber:string = 'seven';
myFavoritenumber = 1;
//Type '1' is not assignable to type 'string'.

如果是any类型,则允许被赋值为任意类型

let myFavoriteNumber:any = 'seven';
myFavoritenumber = 1;

2.2 任意值的属性和方法

在任意值上访问任何属性、方法都是被允许的:

let anyThing:any = 'Hello';
console.log(anyThing.myName);

let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

2.3 未声明类型的变量

变量如果在声明时,未指定其类型,那么它会被识别为任意值类型:

let sonething;
something = 'string';
something = 1;
something.setName('Tom');

//等价于
let sonething:any;
something = 'string';
something = 1;
something.setName('Tom');

3. 类型推论

TypeScript会在没有明确指明类型的情况下,推测出一个类型,这就是类型推论
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查。

4. 联合类型

联合类型表示取值可以是多种类型中的一种。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

联合类型用 | 分隔不同类型

注意:访问联合类型的属性或者方法需要访问所有已定类型公有的属性或者方法

let myFavoriteNumber: string | number;
console.log(myFavoriteNumber.length)
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
// 报错原因是length是string数据类型的属性,但不是number类型的属性

console.log(myFavoriteNumber.toString())  //right
// toString()方法是string和number类型公有的方法

联合类型的变量在被赋值的时候,会根据被赋值的值的类型推断变量的类型。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'GG';
myFavoriteNumber.length; //2
myFavoriteNumber = 6;
myFavoriteNumber.length; // Property 'length' does not exist on type 'number'.

5.对象的类型——接口

在TypeScript中我们使用接口(Interfaces)来定义对象的类型.

5.1 什么是接口

在面向对象的语言中,接口是对行为的抽象,而具体行为是靠类(class)来实现(implement)

5.2 举个栗子:
interface Person{
  name:string;
  age:number;
}
let Tom:Person = {
  name:'Tom',
  age:12
} 

//就目前这个例子,对于接口的理解就是将对像的属性类型提前定义好。
//我们约束了tom的形状必须和Person一致。

定义的变量比接口少一些或者多一些属性都是不被允许的:

interface Person{
  name:string;
  age:number;
}
let Tom:Person = {
  name:'Tom'
}
//Type '{ name: string; }' is not assignable to type 'Person'.
//Property 'age' is missing in type '{ name: string; }'.

5.3 可选属性

如果想让变量的形状和接口的形状不完全一致,需要设置可选属性: age?:number;

interface Person{
  name:string;
  age?:number;
}
let Tom:Person = {
  name:'Tom'
}

5.4 任意属性
interface Person{
  name:string;
  age?:number;
  [propName:string]:any
}
let Tom:Person = {
  name:'Tom',
  gender:'female'
}

注意:一旦定义了任意属性,已经确定属性的类型和可选属性的类型必须是任意属属性的类型的子集

5.5 只读属性

如果我们想对象的某些字段只能在创建的时候被赋值,那么可以在接口的属性前加readonly

interface Person{
  readonly id:number;
}
let tom:Person = {
  id:123456
}

tom.id = 342143;
//Cannot assign to 'id' because it is a read-only property.

注意:只读属性约束的是第一次在给对象赋值的时候,而不是第一次给只读属性赋值的时候:

interface Person{
  readonly id:number;
  name:string;
}
let tom:Person = {
  name:'gg'
}

tom.id = 342143;
//Property 'id' is missing in type '{ name: string; gender: string; }' but required in type 'Person'.
//Cannot assign to 'id' because it is a read-only property.
//两个报错一个是说该复赋值的属性,你不赋值,错!
//另一个是说,属性赋值后补是不可以的。

注意:只要我们给对象的某个属性设置了只读属性,在给对象赋值的时候,如果没有及时给只读属性进行赋值,之后单独给只读属性赋值是不被允许的。

5.6 总结:
  • 接口定义用interface关键字
  • 定义接口属性的时候,如果最初确定了要定义属性的个数,后期在对象中是不能任意加或者减属性的。
  • 如果想更灵活的定义接口属性个数,需要加【propName:string】:any ,这意思是后期对象赋值时,属性随便加,但是有一点,任意属性的类型必须是包含接口其他属性定义的类型。
  • 接口中定义的某个属性,想在对象赋值的时候想赋值就赋值,不想赋值就不赋值,需要在接口属性后加?。
  • 只读属性,在给对象赋值的时候就给该属性赋值,否则后期想加或者想改是没机会了。

6. 数组类型

6.1 「类型 + 方括号」表示法
let arr:number[] = [1,2,3,4,5];
let arr:number[] = ['1',2,3,4,5];
//error TS2322: Type 'string' is not assignable to type 'number'.
arr.push('0');
//error TS2345: Argument of type '"2"' is not assignable to parameter of type 'number'.

注意:上面已经确定了数组元素的类型,如果元素中数据类型是非定义的则会报错,如果后追加的元素是非定义的同样会报错。

6.2 数组泛型

我们也可以使用数组泛型,Array来表示数组:

let arr:Array<number> = [1,2,3,4,5];

6.3 接口表示数组
interface NumberArray{
	[index:number]:number
}

let arr:NumberArray = [1,2,3,4];

NumberArray表示数组的索引是数字类型表示,数组的元素也必须用数字类型表示。
一般情况我们不用这种方式表示数组,但是在表示类数组的情况通常用这种写法。

6.4 类数组

类数组(array-link object)不是数组类型,比如arguments:

function sum(){
    let args:number[] = arguments;
}

sum(1,2,3);
//Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 15 more.
//Expected 0 arguments, but got 3.
//类数组不是数组,所以不能用数组类型表示

arguments是一个类数组,不是普通数组,所以需要用接口表示

function sum(){
    let args:{
        [index:number]:number;
        length:number;
        callee:Function;
    } = arguments;
}

//callee被调用者;被调用函数

事实上,常用的类数组都有自己的接口定义,IArguments、NodeList、HTMLCollection等;

function sum(){
	let args:IArrguments = arguments;
}

其中IArrguments是TypeScript定义好的类型:

interface IArrguments{
	[index:number]:number;
  length:number;
  callee:Function;
}

6.5 any在数组中的应用
let arr:any[] = {'1',3,{'h':2}}

7. 函数类型

7.1 函数声明

在JavaScript中,常见的函数定义的方式——函数声明和函数表达式

//函数声明
function sum(x,y){
	return x+y;
}
//函数表达式
let sum = function(x,y){
	retuen x+y;
}

一个函数有输入有输出,要在TypeScript中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义比较简单:

function sum(x:number,y:number):number{
	return x+y;
}

注意:输入多余或者少于要求的参数,是不被允许的:

function sum(x:number,y:number):number{
	return x+y;
}
sum(1,2,3);
sum(1);
//index.ts(4,1): error TS2346: 
//Supplied parameters do not match any signature of call target.

7.2 函数表达式
let muSum = function (x:number,y:number):number{
	return x+y;
}

这种写法虽然可以编译,但是不完全正确

let muSum:(x:number,y:number)=>number = function (x:number,y:number):number{
	return x+y;
}

注意:上面的=> 不同于ES6中的箭头函数

7.3 用接口定义函数的形状
interface SearchFunc{
  (source:string,subString:string):boolean;
}

let muSearch:SearchFunc;
mySearch = function(source:string,subString:string):boolean{
	return source.search(subString)!==-1;
}

7.3 可选参数

在参数名后面加‘ ?’

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

注意:可选参数后面不能有必需参数了

7.4 参数默认值
function buildName(firstName: string,lastName: string='hh'){
	return firstName+lastName;
}

注意;此时可选参数可以不是在必需参数后面了;

7.5 剩余参数

ES6中用…rest来接收剩余参数

function arr(arr,...items){
  items.forEach(function(i){
  	arr.push(i);
  });
  return arr;
}
arr([],1,2,3);

注意:rest参数,只能是最后一个参数。

7.6 重载

重载允许一个函数接受不同数量,不同类型的参数,做出不同处理

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

我们定义了多次reverse函数,前几次都是函数的定义,最后一次是函数的实现,TypeScript会从最前面的定义开始匹配,所以函数定义有包含关系,要不精确定义写在前面。

8. 类型断言

类型断言(Type Assertion)可以手动指定一个值的类型。

8.1 语法

<类型>值  或  值 as 类型 
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。

8.2 断言的作用

联合类型中,对于参数类型不确定,如果想调用参数的方法或者属性,需要是已经定义的类型的公有方法或属性,使用起来极其不便,所以延伸出断言,就是提前断定该参数是什么类型的。

function reverse(item:string|number):string{
	if((<string>item).length){
  	return (<string>item).length;
  }else{
    return item.toString().length;
  }
}

注意:断言不是类型转换,断言成联合类型中不存在的类型是不允许的。
断言在联合类型中更好的使用了不同类型数据的方法或者属性。

9. 声明文件

当时用第三方库的时候,我们需要引用他们的声明文件,才能获得对应的代码补全和接口提示等功能。

9.1 新语法索引
  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型
  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块
  • export as namespace UMD 库声明全局变量
  • declare global 扩展全局变量
  • declare module 扩展模块
  • ///  三斜线指令

9.2 什么是声明语句

如果我们想在TypeScript中使用第三方库jQuery,常见方式是通过

declare var jQuery:(selector:string)=>any;
jQuery('#id')

9.3 什么是声明文件

通常我们会把声明语句放在一个单独的文件(jQuery.d.ts)中:

//src/jQuery.d.ts
declare var jQuery:(selector:string):any;

注意:声明文件必须以 ‘.d.ts’ 结尾

9.4 第三方声明文件

jQuery声明文件不需要我们定义,因为社区已经为我们定义好了
我们可以直接下载下来使用,但是更推荐的是使用 @types 统一管理第三方库的声明文件。
@types 的使用方式很简单,直接用npm安装对应的声明模块就可以了,以jQuery为例:
npm install [@types](#)/jquery --save-dev

可以在这个页面搜索你需要的声明文件。

9.5 书写声明文件

库的使用场景主要有以下几种:

9.5.1 全局变量

全局变量的声明文件主要有以下几种语法:

9.5.1.1 declare var 声明全局变量
声明文件:

//src/jquery.d.ts
//声明语句:
declare let jQuery:(selector:string):any;
//src.index.ts
jQuery('#foo');
//使用declare let 定义的jquery类型,允许修改这个全局变量。
jQuery = function(){
	return document.querySelector(selector);
}

如果我们使用const定义时,就不能对这个变量进行修改了。
注意:声明语句只能定义变量类型,不能在里面定义具体的实现。

9.5.1.2 declare function 声明全局方法

declare function jQuery(selector:string)=>any;

在函数类型的声明语句中,也是支持重载的:

// src/jQuery.d.ts
declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
// src/index.ts
jQuery('#foo');
jQuery(function() {
    alert('Dom Ready!');
});

9.5.1.3 declare class 声明全局类

declare class Animal{
	name:string;
  constructor(name:string);
  sayHi():string;
}
let cat = new Animal('Tom');

同样的,declare class 只能定义类型,不能定义具体的实现。

9.5.1.4 declare enum 声明全局枚举类型
使用declare enum定义的枚举类型也称外部枚举

declare enum Directions{
	Up,
  Down,
  Left,
  Right
}
// src/index.ts

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

9.5.1.5 declare namespace 声明(含有子属性的)全局对象

// src/jQuery.d.ts

declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
}
  
// src/index.ts

jQuery.ajax('/api/get_something');

注意,在 declare namespace 内部,我们直接使用 function ajax 来声明函数,而不是使用 declare function ajax。类似的,也可以使用 const, class, enum 等语句:

// src/jQuery.d.ts

declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
    const version: number;
    class Event {
        blur(eventType: EventType): void
    }
    enum EventType {
        CustomClick
    }
}
  
  
// src/index.ts

jQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);

9.5.1.6 嵌套的命名空间
如果对象有深层次的层级,则需要nameapace来声明深层次的属性的类型

// src/jQuery.d.ts
declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
    namespace fn {
        function extend(object: any): void;
    }
}

// src/index.ts
jQuery.ajax('/api/get_something');
jQuery.fn.extend({
    check: function() {
        return this.each(function() {
            this.checked = true;
        });
    }
});

假如jQuery下面只有fn这一个属性:

// src/jQuery.d.ts
declare namespace jQuery.fn {
    function extend(object: any): void;
}

// src/index.ts
jQuery.fn.extend({
    check: function() {
        return this.each(function() {
            this.checked = true;
        });
    }
});

9.5.1.7 infterface和type

防止命名冲突:将interface放在jQuery下面:

// src/jQuery.d.ts
declare namespace jQuery {
    interface AjaxSettings {
        method?: 'GET' | 'POST'
        data?: any;
    }
    function ajax(url: string, settings?: AjaxSettings): void;
}
  
// src/index.ts
let settings: jQuery.AjaxSettings = {
    method: 'POST',
    data: {
        name: 'foo'
    }
};
jQuery.ajax('/api/post_something', settings);

9.5.2 声明合并

jQuery既是一个函数又是一个对象,有多个方法可以被调用:

declare function jQuery(selector:string):any;
declare namespace jQuery{
	function ajax(url:string,setting?:any):any;
}

9.5. npm包

一般我们通过 import foo from 'foo' 导入一个 npm 包,这是符合 ES6 模块规范的。
在我们尝试给一个 npm 包创建声明文件之前,需要先看看它的声明文件是否已经存在。一般来说,npm 包的声明文件可能存在于两个地方:

  1. 与该 npm 包绑定在一起。判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。
  2. 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,安装命令是 npm install @types/foo --save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到 @types 里了。

假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了。由于是通过 import 语句导入的模块,所以声明文件存放的位置也有所约束,一般有两种方案:

  1. 创建一个 node_modules/@types/foo/index.d.ts 文件,存放 foo 模块的声明文件。这种方式不需要额外的配置,但是 node_modules 目录不稳定,代码也没有被保存到仓库中,无法回溯版本,有不小心被删除的风险,故不太建议用这种方案,一般只用作临时测试。
  2. 创建一个 types 目录,专门用来管理自己写的声明文件,将 foo 的声明文件放到 types/foo/index.d.ts 中。这种方式需要配置下 tsconfig.json 中的 pathsbaseUrl 字段。

npm 包的声明文件主要有以下几种语法:

9.5.export

。。。未完待续


10. 内置对象

10.1 ECMAScript 内置对象

Boolean、Error、Date、RegExp
我么可以在TypeScript中将变量定义成这些类型:

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

10.2 DOM、BOM内置对象

Document、HTMLElement、Event、NodeList:

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

10.3 TypeScript核心库定义文件

10.4 用TypeScript写Node.js
npm install @types/node --save-dev
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值