初见 TypeScript
Posted WeCode365
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初见 TypeScript相关的知识,希望对你有一定的参考价值。
欢迎查查的加入,以后我们就要互相督促坚持写文章、做分享咯(这里手动艾特查查,这周你需要有一篇分享哦~)
然后我们就要今天的正题了,就是大家都听说过的 TypeScript。
什么是 TypeScript
TypeScript 是 javascript 的一个超集,主要提供了类型系统和对 ES6 的支持。
为什么选择 TypeScript
代码的可读性和可维护性
兼容 JavaScript
活跃的社区(很多第三放库都是用 TypeScript 编写的)
引导示例
首先来看一个 TS 示例:
functuon sayHi(name: string): string {
return `hi ${name}`;
}
let userName = 'Andy';
console.log(sayHi(useName));
在 TypeScript 中,我们使用:
来指定类型,前后的空格不做限制。在上面的示例中,我们指定了参数name
的类型是string
,函数的返回值类型是string
。
编译后:
function sayHi(name) {
return 'hi ' + name;
}
var userName = 'Andy';
console.log(sayHi(userName));
在编译后,检查类型的代码会被删除,这是因为 TypeScript 只做静态类型检查,如果发现错误,会在编译的时候报错,但是这不影响编译过程,编译结果仍然会被正常的输出:
function sayHi(name: string): string {
return `hi ${name}`;
}
let userName = 10086;
console.log(sayHi(userName));
// 编译时会报错
// error: Argument of type 'number' is not assignable to parameter of type 'string'.
// 但是仍然会生成和上面一样编译结果,我们仍然可以使用这个编译后的文件。
Tips: 如果想在编译报错的时候停止生成 js 文件,可以在
tsconfig.json
中配置noEmitOnError
即可。
原始数据类型
JS 中的数据类型分为原始数据类型和对象类型,这里主要介绍原始数据类型(不包括 ES6 中引入的 Symbol 类型)
数值
// 使用 'number' 定义类型
let age: number = 23;
let notANumber: number = NaN;
let infinityNum: number = Infinity;
Tips: 数值类型可以是数字、二进制数、八进制数、NaN、Infinity
字符串
// 使用 'string' 定义类型
let name: string = 'Tom';
let selfIntroducr: string = `My name is ${name}`;
布尔值
// 使用 'boolean' 定义类型
let isSuccess: boolean = true;
Tips: 针对上面三种类型,不能使用构造函数赋值
typescript
let name: string = new String('Tom'); // errorlet age: Number = new Number(1);let isTrue: boolean = Boolean(1);
空值
// 使用 'void' 定义类型
function sayName(name: string): void {
alert(`My name is ${name}`);
}
// 空值只能被赋值为 null 和 undefined
let nullValue: void = null;
null 和 undefined
// 使用 'null' 、'undefined' 自身来定义类型
let nullValue: null = null;
let undefValue: undefined = undefined;
Tips: null 和 undefined 是所有类型的子类型,如:
let age: number = undefined;
任意值类型
任意值类型可以被随意赋值或改变类型。
// 使用 'any' 定义类型
let canBeAny: any = 'nine';
canBeAny = 9;
// any 类型可以访问任何属性和方法
canBeAny.props = 'button';
canBeAny.setProps({button: 'button', label: 'label'});
Tips: 定义一个变量时若不指定类型则默认为任意值类型
联合类型
用于定义一个变量可以为多种类型之一。
let unionValue: string | number = 'zero';
unionValue = 0
联合类型只能访问联合类型中所有类型共有的属性或方法:
function converToStr(value: string | number): string {
return value.toString();
}
// 错误示例
function getLen(value: string | number): number {
return value.length;
}
// 因为 'length' 不是 'string' 和 'number' 的共有属性,因此在编译时会报错。
接口类型
在面向对象编程中,接口可以对类进行部分抽象,然后由类去实现接口,在 TS 中它还有另外一个作用,可以被用来描述对象。
interface Person {
name: string;
age: number;
}
let andy: Person = {
name: 'Andy',
age: 18
}
// 接口定义的变量不允许增加或缺少属性
// 错误示例:
let amy: Person = {
name: 'Amy'
} // error: 缺少 age
let tom: Person = {
name: 'Tom',
age: '19',
sex: 'male'
} // error: 未知的 sex
接口中的可选属性
在属性名后加?
用于表示该属性为可选属性:
interface City {
name: string;
province?: string;
}
// 使用可选属性后,可以缺省该属性
let nanJing: City = {
name: 'NanJing'
}
只读属性
在属性名前加上readonly
表示该属性为只读属性:
interface City {
readonly id: number;
name: string;
province?: string;
}
let nanJing: City = {
id: 10001,
name: 'NanJing',
province: 'JiangSu'
}
nanJing.id = 10002; // error: id is read-only
任意属性
用于兼容可能存在未定义类型的数据:
interface Person {
name: string;
age: number;
}
let andy: Person = {
name: 'Andy',
age: 18,
sex: 'male'
}
Tips: 任意属性的类型必须包含确定属性、可选属性的类型```typescriptinterface Person {name: string;age?: number;propName: string: string} // error: string 类型不包含 number 类型
类型推论
如果你在定义一个变量时没有指定类型,那么 TS 会按照类型推论推断出一个类型。看一个例子:
let numberValue = 0; // 类型推论会把 numberValue 的类型设为 number
numberValue = 'zero'; // error: string 不能赋值给 number 类型
let someValue; // 若没有给出初始值,则被推论为为 any 类型
someValue = 0;
someValue = 'zero';
数组类型
数组类型的定义方式:
// 定义方式一:类型 + 方括号
let arr: number[] = [1,2,3];
// 定义方式二:数组泛型
let arr: Array<number> = [1,2,3];
// 定义方式三:接口
interface IArr = {
}
let arr: IArr = [1,2,3];
函数类型
函数定义的两种方式:
// 函数声明
function add(x: number, y: number): number {
return x + y;
}
// 函数表达式
let del = function(x: number, y: number): number {
return x - y;
}
Tips: 使用函数的时候不允许缺省、增加参数
函数表达式
上面例子中,del
函数是通过函数表达式的方式定义的,可以正常编译,但是实际上这里完整的定义应该是这样:
let del: (x: number, y: number) => number = function(x: number, y: number): number {
return x - y;
}
// 注意!这里的 '=>' 不是 ES6 中的箭头函数
之所有可以正常编译,是因为这里可以通过类型推论推断出来。
用接口定义函数
interface IDel {
(x: number, y: number): number;
}
let del: IDel = function(x: number, y: number) {
return x - y;
}
参数相关
可选参数
在参数名后加?
表示为可选参数:
function getNameAndSex(name: string, sex?: number): string {
if(sex) {
return `${name}, ${sex}`;
} else {
return name;
}
}
Tips: 可选参数必须存在于必须参数后面,可选参数后不可再有必须参数
参数默认值
与可选参数不同点在于,有默认值的参数不用写在必须参数后面,但是需要传入一undefined
来获取默认值:
let defaultName = 'Tom';
function buildName(firstName: string = defaultName, lastName: string): string {
return firstName + ' ' + lastName;
}
buildName(undefined, 'Cat');
剩余参数
在 ES6 中可以使用...
的方式获取剩余参数,在 TS 中也同样可以用数组来定义它:
function push(arr: any[], ...items: any[]): void {
items.forEach(item => {
arr.push(item);
})
}
let array: any[] = [1, 2, '3'];
push(array, 4, 5, '6');
重载
由于函数的入参不同,返回的数据类型也会不同,重载可以针对不同的入参做出不同的处理。看一个简单的例子:
function selfAdd(x: number | string): number | string {
if(typeof x === 'number') {
return x * 2;
} else if(typeof x === 'string') {
return x + x;
}
}
上面这个例子可以根据不同的参数返回不同的结果,但是表达的并不够清楚,我们期待的是传入的参数的类型与返回值的类型完全相同,而不是联合类型中的一种,这个时候重载就可以派上用场了。
function selfAdd(x: number): number;
function selfAdd(x: string): string;
function selfAdd(x: number | string): number | string {
if(typeof x === 'number') {
return x * 2;
} else if(typeof x === 'string') {
return x + x;
}
}
在上面这个例子中,我们重复定义了selfAdd
,最后是对它的实现。在我们编写代码的时候,编辑器会给出我们上面两个定义的提示。
Tips:在编译的时候会从最前面的函数定义开始匹配,所以我们应该把最精确的定义写在最前面
断言
类型断言可以用于指定某个参数的类型,即向程序保证该处的参数一定是某个类型,先看之前的一个例子:
function getLen(value: string | number): number {
if(value.length) {
return value.length;
} else {
return value.toString().length;
}
}
error // 因为 'length' 不是 'string' 和 'number' 的共有属性,因此在编译时会报错。
这时我们可以使用断言,将 value 断言成 string 类型:
function getLen(value: string | number): number {
if((<string>value).length) {
return (<string>value).length;
} else {
return value.toString().length;
}
}
// 我们只需要在需要断言的变量前加上它的类型即可
断言的两种方式:
-
value value as Type
Tips: 断言不是类型转换,断言必须是联合类型中的一种
元组
元组和数组不同在于数组内的对象类型相同,而元组内的对象可以是不同类型。元组相关示例:
// 定义:
let tuple: [number, string] = [1, '1'];
// 访问元组内的元素:
tuple[0]; // 1
// 赋值
tuple[1] = '10086';
// 错误示例:
// 在初始化或赋值的时候,元组中指定的项必须赋值
let tuple2: [number, string] = [1] // error
let tuple3: [number, string];
tuple3 = 1; // error
// 单独赋值是被允许的
tuple3[0] = 1;// true
// 可以越界,类型需是元组指定类型的联合类型
let tuple4: [number, string] = [1, '2', '3333'] // true
枚举
我们使用枚举主要是用来限定取值在一定的范围内,使用 enum
来定义枚举类型。
enum Directions {
Up,
Down,
Left,
Right
}
我们也可以为枚举项手动赋值:
enum Directions {
Up = 'up',
Dowm = 'down',
Left = 'left',
Right = 'right'
}
// 如果不赋值的话会默认为: 0、1、2...
枚举会被编译成对象,并且包含双向映射:key -> value
和 value -> key
。
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
编译后:
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
枚举可以包含计算成员:
enum Enum {
A,
B,
C = 'color'.length
}
Tips: 计算成员后的所有项必须手动赋值,否则编译会报错。
常数枚举
常数枚举 和 枚举的区别在于常数枚举在编译的时候会被删除并且当中不能包含计算成员。
使用 const enum
来定义:
const enum Directions {
Up,
Down,
Left,
Right
}
// 编译后
var Directions = [0, 1, 2, 3];
类
从 ES6 开始,js 开始支持使用 class
来定义类。类有以下几个主要特点:
面向对象:封装、继承和多态
包含属性和方法
使用
new
关键字来生成实例修饰符:
public
、protected
、private
、static
存取器:
getter()
、setter()
抽象类:提供用于继承的基类,不允许被实例化,其包含的抽象方法必须在非抽象子类中实现
接口:一个类只可以继承一个类,但是可以实现多个接口,接口的功能类似于抽象方法
定义
class Plants {
name: string;
constructor(name: string) {
this.name = name;
}
sayName(): string {
return `My name is ${this.name}`;
}
}
继承
class Trees extends Plants {
constructor(name: string) {
super(name);
console.log(this.name);
}
sayName(): string {
return `Hi, ${super.sayName()}`
}
}
let appleTree = new Trees('appleTree');
console.log(appleTree.sayName()); // Hi, My name is appleTree;
静态方法
静态方法不需要实例化即可访问:
class Plants {
static sayHi(): void {
alert('Hi~')
}
}
Plants.sayHi();
修饰符
上面已经讲到一个 static
,这里主要介绍其他几个。
class Plants {
name: string; // 未加修饰符,默认为 public,外部可访问
private leaf: boolean; // 私有,外部不可访问
protected kind: string; // 受保护,可以在子类中访问
constructor() {
....
}
}
抽象类 & 抽象方法
抽象方法必须存在于抽象类中,用 abstract
定义。
abstract class Plants {
public name: string;
public constructor(name: string) {
this.name = name;
}
pubilc abstract sayHi();
}
class Trees extends Plants {
constructor(name) {
super(name);
}
public sayHi(): string {
return `Hi, My name is ${this.name}`;
}
}
类与接口
类实现接口:
interface Say {
say();
}
class Trees {
}
class AppleTree extends Trees implements Say {
say() {
alert('Hi');
}
}
Tips: 类可以实现多个接口
接口间的继承
接口可以继承接口:
interface Say {
say();
}
interface Bird extends Say{
fly();
}
接口可以继承类
abstract class Say {
name: string;
public abstract say();
}
interface Bird extends Say{
fly();
}
class Parrot implements Bird {
name: string;
constructor(name) {
this.name = name;
}
fly() { }
say() { }
}
类型别名
使用 type
给类型重新定义一个名字:
type StrOrNum = string | number;
type Person = {
name: string;
age: number;
}
Tips: type 和 接口很相似,但是 type 不可以继承和被继承,通常建议使用 interface 而不是 type.
字面类型
字面值的类型:
type Weather = 'rain' | 'cloud' | 'sunshine';
let todayWeather: Weather = 'sunshine';
泛型
泛型通常用在定义函数、接口、类的时候,我们不指定类型,等到使用的时候再去指定。
泛型函数
function printImports<T>(value: T): T {
console.log(vlaue);
return value;
}
printImports<string>('1234');
在使用泛型定义函数的时候,不能访问未知的属性,除非提前指定:
function getLen<T>(value: T): T {
console.log(value.length); // error, lenght 不一定存在于泛型 T 中
return T;
}
// 需要提前指定是泛型数组
function getLen<T>(value: Array<T>): Array<T> {
console.log(value.length);
return value;
}
//或者通过接口约束泛型
interface Length {
length: number;
}
function getLen<T extends Length>(value: T): number {
console.log(value.length);
return value.length;
}
泛型接口
我们可以用接口来定义函数的形状:
interface IGetLen {
<T>(value: Array<T>): number;
}
// 函数实现
let getLen: IGetLen;
getLen = function<T>(value: Array<T>): number {
console.log(value.length);
return value.length;
}
// 或者
interface IGetLen<T> {
(value: T[]): number;
}
// 函数实现
let getLen: IGetLen<any>;
getLen = function<T>(value: Array<T>): number {
console.log(value.length);
return value.length;
}
泛型类
与接口类似:
class Plants<T> {
name: T;
constructor(name: T) {
this.name = name;
}
sayName(): void {
alert(this.name);
}
}
let appleTree = new Plants<string>('APPLE TREE')
声明合并
在 TS 中,允许定义重名的函数、接口、类,他们会被合并成一个类型。
函数的合并
前面我们见过,函数的重载,实际上就是函数声明的合并:
function selfAdd(x: number): number;
function selfAdd(x: string): string;
function selfAdd(x: number | string): number | string {
if(typeof x === 'number') {
return x * 2;
} else if(typeof x === 'string') {
return x + x;
}
}
接口的合并
接口合并示例:
interface Person {
name: string;
}
interface Person {
age: number;
}
// 合并后相当于
interface Person {
name: string;
age: number;
}
如果被合并的接口中有重复的属性,那么类型必须相同:
interface Person {
age: string;
}
interface Person {
name: string;
age: number; // error: 类型不一致
}
接口中的方法合并,类似于函数的重载:
interface Person {
say(name: string): string;
}
interface Person {
say(name: string, age: number): string;
}
// 合并后
interface Person {
say(name: string): string;
say(name: string, age: number): string;
}
类的合并
与接口类似。
模块与命名空间
模块
外部模块,使用export
导出,使用import
导入。
示例:
// 导出接口
export interface Person {
...
}
// 重新导出
export * from '...';
// 导出语句
class Person {
}
export { Person };
// 默认导出
export default class Animal {
}
// 导入
import { Person } from '...';
import * as AnotherName from '...';
import Animal from '...';
Tips: 若使用
export = module
导出,则必须使用import module = require(module)
来进行导入。
命名空间
内部模块,使用namespace
定义。示例:
namespace Animal{
export class Cat {
...
}
export class Dog {
...
}
}
let dog: Animal.Dog = new Animal.Dog();
声明文件
我们经常会把类型声明放到一个单独的*.d.ts
文件中。当我们引用第三方库的时候,我们也需要引用他们的声明文件:
yarn add axios // axios 本身就是由 TS 编写的,包含了声明文件
yarn add jsonp @types/json // jsonp 库没有包含,需要单独再引入声明文件
声明文件的编写
这里用JQuery
作为示例:
// jQuery.d.ts
declare var jQuery: (string) => any; // 声明语句
// 在用到 JQuery.d.ts 的文件开头,用 '三斜线指令'表示引入声明文件
/// <reference path="./jQuery.d.ts" />
jQuery('#foo');
总结
TypeScript 还有很多高级用用法,这里只是做基础的介绍,更多用法可以去官方文档查看。
本文同步发布于同名知乎专栏:WeCode365 ,记得关注哦~
以上是关于初见 TypeScript的主要内容,如果未能解决你的问题,请参考以下文章
typescript Angular最终版本的Angular 2测试片段。代码库https://developers.livechatinc.com/blog/category/programming
typescript Angular最终版本的Angular 2测试片段。代码库https://developers.livechatinc.com/blog/category/programming
typescript Angular最终版本的Angular 2测试片段。代码库https://developers.livechatinc.com/blog/category/programming