专栏首页全栈修仙之路TypeScript 简介及编码规范

TypeScript 简介及编码规范

TypeScript 是什么

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript 扩展了 JavaScript 的句法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。TypeScript 是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。

  • 安装 TypeScript
$ npm install -g typescript
  • 编译 TypeScript 文件
$ tsc app.ts # app.ts => app.js

TypeScript 基础类型

Boolean 类型

let isDone: boolean = false; // tsc => var isDone = false;

Number 类型

let count: number = 10;  // tsc => var count = 10

String 类型

let name: string = 'Semliker'; // tsc => var name = 'Semlinker'

Array 类型

let list: number[] = [1,2,3]; // tsc => var list = [1,2,3];   

let list: Array<number> = [1,2,3]; 
// tsc => var list = [1,2,3];

Enum 类型

enum Direction {
	NORTH,
    SOUTH,
    EAST,
    WEST
};

let dir: Direction = Direction.NORTH;

默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。

当然我们也可以设置 NORTH 的初始值,比如:

enum Direction {
	NORTH = 3,
  SOUTH,
  EAST,
  WEST
};

在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

enum Direction {
  NORTH = 'NORTH',
  SOUTH = 'SOUTH',
  EAST = 'EAST',
  WEST = 'WEST',
}

Any (动态类型)

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; 

=> tsc =>  

var notSure = 4;
notSure = "maybe a string instead";
notSure = false;

Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为 stringnumber 类型的元组。

let x: [string, number];

x = ['semlinker', 10]; // 正常赋值

x = [10, 'semlinker']; // 类型不匹配

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)); // OK

// Error, 'number' does not have 'substr' method
console.log(x[1].substr(1));

当访问一个越界的元素,会使用联合类型替代:

x[3] = 'world'; // OK, 字符串可以赋值给(string | number) 类型

console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString 方法

x[6] = true; // Error, 布尔不是(string | number) 类型

Void

某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:

// 声明函数返回值为void
function warnUser(): void { 
    console.log("This is my warning message");
}

=> tsc =>  

function warnUser() {
	console.log("This is my warning message");
}

需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefinednull

let unusable: void = undefined;

Null and Undefined

TypeScript 里,undefinednull 两者各自有自己的类型分别叫做 undefinednull

let u: undefined = undefined;
let n: null = null;

默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 nullundefined 赋值给 number 类型的变量。然而,当你指定了--strictNullChecks 标记,nullundefined 只能赋值给 void 和它们各自。

Never

never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {}
}

TypeScript Assertion

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,”相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式:

  • “尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
  • as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

TypeScript Union Types and Type Aliases

Union Types

let greet = (message: string | string[]) => {
  if(message instanceof Array) {
    let messages = "";
    message.forEach((msg) => {
      messages += ` ${msg}`;
    });
    console.log("Received messages ", messages);
  } else {
    console.log("Received message = ", message);
  }
};

greet('semlinker');
greet(['Hello', 'Angular']);

Type Aliases

type Message = string | string[];
let greet = (message: Message) => { 
  // ... 
};

TypeScript Function

TypeScript 函数与 JavaScript 函数的区别

TypeScript

JavaScript

Types

No types

Arrow function

Arrow function (ES2015)

Function types

No Function types

Required and Optional parameters

All parameters are optional

Default parameters

Default parameters

Rest parameters

Rest parameters

Overloaded function

No overloaded functions

箭头函数

  • 常见语法
myBooks.forEach(() => console.log('Done reading'));

myBooks.forEach(title => console.log(title));

myBooks.forEach((title, idx, arr) => 
  console.log(idx + '-' + title);
);

myBooks.forEach((title, idx, arr) => {
  console.log(idx + '-' + title);
});
  • 使用示例
// 未使用箭头函数
function Book() {
  let self = this;
  self.publishDate = 2016;
  setInterval(function() {
    console.log(self.publishDate);
  }, 1000);
}

// 使用箭头函数
function Book() {
  this.publishDate = 2016;
  setInterval(() => {
    console.log(this.publishDate);
  }, 1000);
}

参数类型和返回类型

function createUserId(name: string, id: number): string {
  return name + id;
}

函数类型

let IdGenerator: (chars: string, nums: number) => string;

function createUserId(name: string, id: number): string {
  return name + id;
}

IdGenerator = createUserId;

可选参数及默认参数

// 可选参数
function createUserId(name: string, age?: number, 
  id: number): string {
    return name + id;
}

// 默认参数
function createUserId(name: string = 'Semlinker', age?: number, 
  id: number): string {
    return name + id;
}

剩余参数

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

TypeScript Array

数组解构

let x: number, let y: number ,let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;

数组展开运算符

let two_array = [0,1];
let five_array = [...two_array,2,3,4];

数组循环

let colors: string[] = ["red", "green", "blue"];
for(let i in colors) {
  console.log(i);
}

TypeScript Object

对象解构

let person = {
  name: 'Semlinker',
  gender: 'male'
};

let {name, gender} = person;

对象展开运算符

let person = {
  name: 'Semlinker',
  gender: 'male',
  address: 'Xiamen'
};

// 组装对象
let personWithAge = {...person, age: 31};

// 获取除了某些项外的其它项
let {name, ...rest} = person;

TypeScript Interface

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

对象的形状

interface Person {
  name: string;
  age: number;
}

let semlinker: Person = {
  name: 'Semlinker',
  age: 31
};

可选 | 只读属性

interface Person {
  readonly name: string;
  age?: number;
}

只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray<T> 类型,它与 Array<T> 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

TypeScript Class

在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。

在 TypeScript 中,我们可以通过 Class 关键字来定义一个类:

class Greeter {
   static cname: string = 'Greeter'; // 静态属性
   greeting: string; // 成员属行

   constructor(message: string) { // 构造函数 - 执行初始化操作
     this.greeting = message;
   }

    static getClassName() { // 静态方法
      return 'Class name is Greeter';
    }
    
    greet() { // 成员方法
      return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

TypeScript Accessors

在 TypeScript 中,我们可以通过 gettersetter 方法来实现数据的封装和有效性校验,防止出现异常数据。

let passcode = "hello angular 5";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "hello angular 5") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

TypeScript Inheritance

继承 (Inheritance) 是一种联结类与类的层次模型。指的是一个类 (称为子类、子接口) 继承另外的一个类 (称为父类、父接口) 的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种 is-a 关系。

在 TypeScripe 中,我们可以通过 extends 关键字来实现继承:

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);
    }
}

let sam = new Snake("Sammy the Python");
sam.move();

TypeScript Generics

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

泛型接口

interface GenericIdentityFn<T> {
    (arg: T): T;
}

泛型类

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

使用示例

interface Hero { // Hero 接口
    id: number;
    name: string;
}

getHeroes(): Observable<Hero[]> {
  return Observable.of([
     { id: 1, name: 'Windstorm' },
     { id: 13, name: 'Bombasto' },
     { id: 15, name: 'Magneta' },
     { id: 20, name: 'Tornado' }
  ]);
}

上面 getHeroes(): Observable<Hero[]> 表示调用 getHeroes() 方法后返回的是一个 Observable 对象,<Hero[]> 用于表示该 Observable 对象的观察者,将会收到的数据类型。示例中表示将会返回 <Hero[]> 英雄列表。

tsconfig.json 简介

tsconfig.json 的作用

  • 用于标识 TypeScript 项目的根路径;
  • 用于配置 TypeScript 编译器;
  • 用于指定编译的文件。

tsconfig.json 重要字段

  • files - 设置要编译的文件的名称;
  • include - 设置需要进行编译的文件,支持路径模式匹配;
  • exclude - 设置无需进行编译的文件,支持路径模式匹配;
  • compilerOptions - 设置与编译流程相关的选项。

compilerOptions 支持很多选项,常见的有 baseUrltargetbaseUrlmoduleResolutionlib 等。

tsconfig.json 示例

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ],
    "paths": {
      "ngx-example-library": [
        "dist/ngx-example-library"
      ]
    }
  },
  "angularCompilerOptions": {
    "preserveWhitespaces": false
  }
}

(示例来源 - angular6-example-app

编码规范

变量和函数

使用驼峰(camelCase)命名变量和函数名

Bad

var FooVar;
function BarFunc() { }

Good

var fooVar;
function barFunc() { }

使用帕斯卡(PascalCase)命名类名

Bad

class foo { }

Good

class Foo { }

使用帕斯卡(PascalCase)命名类成员与方法

Bad

class Foo {
    Bar: number;
    Baz() { }
}

Good

class Foo {
    bar: number;
    baz() { }
}

接口

  • 使用帕斯卡(PascalCase)命名接口
  • 使用驼峰(camelCase)命令成员
  • 误使用 I 前缀

Bad

interface IFoo { }

Good

interface Foo { }

类型

  • 使用帕斯卡(PascalCase)命名
  • 使用驼峰(camelCase)命令成员

命名空间

使用帕斯卡(PascalCase)命名

Bad

namespace foo { }

Good

namespace Foo { }

枚举

使用帕斯卡(PascalCase)命名枚举

Bad

enum color { }

Good

enum Color { }

使用帕斯卡(PascalCase)命名枚举成员

Bad

enum Color {
    red
}

Good

enum Color {
    Red
}

Null vs Undefined

最好不好显式使用不可用的值

Bad

let foo = { x:123, y:undefined };

Good

let foo: { x:number, y?:number } = { x:123 };

通常使用 undefined( 而不是返回一个类似于 { valid:boolean, value?:Foo } 的对象 )

Bad

return null;

Good

return undefined;

参考 Node.js 回调函数 Error First 风格(若未发生异常,error 参数值设置为 null)

Bad

cb(undefined)

Good

cb(null)

避免使用值比较判断对象是否为 null 或 undefined

Bad

if (error === null)

Good

if (error)

数组

声明数组时使用 foos:Foo[] 而不是 foos:Array<Foo>,便于阅读

类型 vs 接口

当你需要复合类型时,使用 type

type Foo = number | { someProperty: number }

当你需要继承或实现时,使用 interface

interface Foo {
  foo: string;
}

interface FooBar extends Foo {
  bar: string;
}

class X implements FooBar {
  foo: string;
  bar: string;
}

风格指南

  1. 使用箭头函数代替匿名函数表达式。
  2. 只要需要的时候才把箭头函数的参数括起来。比如,(x) => x + x 是错误的,下面是正确的做法:
    • x => x + x
    • (x,y) => x + y
    • <T>(x: T, y: T) => x === y
  3. 总是使用 {} 把循环体和条件语句括起来。
  4. 小括号里开始不要有空白。逗号,冒号,分号后要有一个空格。比如:
    • for (let i = 0, n = str.length; i < 10; i++) { }
    • if (x < 10) { }
    • function f(x: number, y: string): void { }
  5. 每个变量声明语句只声明一个变量 。比如:使用 let x = 1; var y = 2; 而不是 let x = 1, y = 2;)。
  6. 如果函数没有返回值,最好使用 void

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 1.8W字|了不起的 TypeScript 入门教程(第二版)

    阿宝哥第一次使用 TypeScript 是在 Angular 2.x 项目中,那时候 TypeScript 还没有进入大众的视野。然而现在学习 TypeScri...

    阿宝哥
  • 了不起的 TypeScript 入门教程

    想学习 TypeScript 的小伙伴看过来,本文将带你一步步学习 TypeScript 入门相关的十四个知识点,详细的内容大纲请看下图:

    阿宝哥
  • TypeScript 非空断言

    使用这种方案,问题是解决了。但有没有更简单的方式呢?答案是有的,就是使用 TypeScript 2.0 提供的非空断言操作符:

    阿宝哥
  • TypeScript 之类型判断

    在使用 Angular 做项目的时候,对 TypeScript 的类型判断不太熟练,为了方便查找,特意对 TypeScript 的类型判断做了简单梳理。

    叙帝利
  • Git LFS(Large File Storage)

    Git Large File Storage (LFS),是GitHub推出的基于Git的扩展,专门用来存储大文件。

    donghui
  • 【Rust每周一库】hyper - 底层http库

    现在说到写应用,网络框架肯定是必不可少的。今天就给大家简单介绍一下hyper。hyper是一个偏底层的http库,支持HTTP/1和HTTP/2,支持异步Rus...

    MikeLoveRust
  • 听说 GitHub 超过 100 MB 无法上传?(Mac OS)

    某天,被水哥,一通教育,深思之后,突然发现工作若干年后,自己都没有维护一个属于自己的东西,尴尬。???

    HLQ_Struggle
  • 【怎么办】003 如何加强Oracle数据库安全--监控数据导入导出操作

    关键字(Keyword):datapummp,audit,unified audit,trigger,触发器,审计,数据库安全

    TeacherWhat
  • git安装及命令使用和github网站

    最近参与别人的github项目时,学习了git的使用,首先需要在https://github.com/网站上注册账号和邮箱,然后fork一个开源项目,然后下...

    ccf19881030
  • 如何存储 Git 大文件?

    ? 作者:terryshchen,腾讯 IEG 应用开发工程师 本文主要讲解在 Git 仓库中如何管理大的二进制文件,详细介绍了什么是 Git LFS,Git...

    腾讯技术工程官方号

扫码关注云+社区

领取腾讯云代金券