TypeScript 的基础篇

639 阅读10分钟

一.TypeScript的介绍

TypeScript是由微软开发的自由和开源的编程语言,是JavaScript的超集,包含了JavaScript的所有元素,可以载入JS运行代码,并扩展了JS的语法。

二.TypeScript vs JavaScript

  • TypeScript 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。

  • JavaScript 代码可以在无需任何修改的情况下与 TypeScript 一同工作,同时可以使用编译器将 TypeScript 代码转换为 JavaScript(注:TypeScript 代码需要被编译成 JavaScript 代码浏览器不支持TypeScript)。

  • TypeScript 通过类型注解提供编译时的静态类型检查。

  • TypeScript 中的数据要求带有明确的类型,JavaScript不要求。

 let isDone: boolean = false; // TypeScript
 let isDone = false;  // JavaScript
  • TypeScript 为函数提供了缺省参数值(xxx? : yyy)。
        interface Person {
            name: string,
            age: number,
            sex?: string
        }
        function sayHello(person: Person): any {
            return `我是${person.name},今年${person.age}岁`
        }
        let user = { name: '小葡萄', age: 12 }
        console.log(sayHello(user));

  • TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中
    class Greeter {
        greeting: string;
        constructor (message: string) {
            this.greeting = message;
        }
        greet() {
            return "Hello, " + this.greeting;
        }
    }  

三.TypeScript的优点

1. 静态输入

静态类型化是一种功能,可以在开发人员编写脚本时检测错误。查找并修复错误是当今开发团队的迫切需求。有了这项功能,就会允许开发人员编写更健壮的代码并对其进行维护,以便使得代码质量更好、更清晰。

2. 帮助更好地重构代码

有时为了改进开发项目,需要对代码库进行小的增量更改。这些小小的变化可能会产生严重的、意想不到的后果,因此有必要撤销这些变化。ts的静态检查特性会帮助找出代码中有错误的部分。

3. 更好的协作

当发开大型项目时,会有许多开发人员,此时乱码和错误的机也会增加。类型安全是一种在编码期间检测错误的功能,而不是在编译项目时检测错误。这为开发团队创建了一个更高效的编码和调试过程。

4. vscode等IDE的提示更加智能

js是一门动态弱类型解释语言,变量声明后可以改变类型,而且类型需要在运行时才能确定。而ts的报错提示是在编译时,不是在运行时。所以使用ts带来的静态类型检查等特性将使得IDE的提示更加完善。

四.TypeScript的使用

官方文档: www.tslang.cn/

1. 基本类型和复合类型

1.1 基本类型:string、number、Array、null、undefined、元组 、枚举enum、Any、void、never
  • string(字符串类型)
// 字符串,单双引都行
let name: string = "bob";
let sentence: string = `Hello, my name is ${ name }`.
  • number(数字类型)
// 数字,二、八、十六进制都支持
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
  • Array(数组类型)
// 数组,第二种方式是使用数组泛型,Array<元素类型>:
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
  • null
let n: null = null;  // 表示一个空对象的引用
  • undefined
let u: undefined = undefined;  // 是一个没有设置值的变量
  • 元组
// 元组:允许表示一个已知元素数量和数组,各个元素的类型不必相同
let x = [string,number]
x=['10',14] //ok
x=[10,14]  // error
x[0].substr(1) // ok // 截取字符串
x[1].substr(1) // error
x[3]='world' // ok
  • enum(枚举)
// 枚举:类型是对JavaScript标准数据类型的一个补充。像C#等其它语言一样,使用枚举类型可以为
一组数值赋予友好的名字
// 默认情况从0开始为元素编号,也可手动为1开始
enum Color {Red,Green,Blue}
let c: Color = Color.Green
enum Color {Red=1,Green,Blur}
let colorName: string = Color[2] // Green
  • Any(任何类型)
// Any: 接入第三库、用户输入时运用
let list: Any[] =['1',2,false]
  • Void(没有任何的类型)
// void: 没有任何的类型(当一个函数没有返回值时,TS中需要定义函数的返回值类型)
let list: void = undefined || null
  • never(永不存在的值的类型)
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}
1.2 复合类型: set 和 map

set 指的是一个无序的、无重复元素的集合。

type Size = 'small' | 'default' | 'big' | 'large';

map指的是没有重复键的键值对。

interface IA {
    a: string
    b: number
}

复合类型之间的转换

//  1.map转成set
type IAKeys = keyof IA;  // 'a'| 'b'
type IAValues = IA[keyof IA]; // // string | number
//  2.set转map
type sizeMap = {
    [k in Size]: number
}
// 等价于
type sizeMap = {
    small: number,
    default: number,
    big: number,
    large: number
}

拓展:位运算符

1001 | 1010 = 1011    // 合并1
1001 & 1010 = 1000    // 只保留共有1

在TypeScript中的使用

interface IA {
	a: string
	b: number
}

type TB = {
	b: number
	c: number[]
}

type TC = IA | TB;    // TC类型的变量的键只需包含ab或bc即可,当然也可以abc都有
type TD = IA & TB;    // TD类型的变量的键必需包含abc

1.3 类型断言 (两种方式:<>和as)

这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

let somethig : any = 'This is an apple';
let length: number = (something as string).length
let length: number = (<string>something).length

2.泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

2.1 泛型的方法

我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值


1、泛型函数
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;
// 调用方式
let output = identity<string>('hello world')
let output = identity('hello world') //第二种调用方式可省略类型参数,因为编译器会根据传入参数来自动识别对应的类型。
2、泛型接口
interface Goods<T>{
    id:number;
    title: string;
    size: T;
}
let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};
let shoes:Goods<number> = {id:1,title: '苹果', size: 43};
2.2 泛型与Any
/ 方法一:带有any参数的方法
function any_func(arg: any): any { // 当传入的数据不是数组、字符串带有length属性的参数时,会抛出异常
 console.log(arg.length);
        return arg;
}
 
// 方法二:Array泛型方法
function array_func<T>(arg: Array<T>): Array<T> { // 传入的是Array的泛型,肯定有length属性
     console.log(arg.length);
        return arg;
}

3.interface(接口) vs type(类型别名))

相同点:

  • 都是描述一个对象或者一个函数
interface User {
    name: string,
    age: number
}
type User = { name: string,age: number}
interface setUser {(name: string, age: number):void}
type SetUser = (name: string, age: number): void;


  • 都可以extends扩展,只是用法不同
// interface extends interface
interface Name { 
 name: string; 
}
interface User extends Name { 
 age: number; 
 }
 
 // type extends type
type Name = { 
 name: string; 
}
type User = Name & { age: number };

//interface extends type
type Name = { 
 name: string; 
}
interface User extends Name { 
 age: number; 
}

//type extends interface
class Name {
    name:string
}
type User= Name & {age:number};

不同点:

  • type 可以声明基本类型别名,联合类型,元组等类型要,要扩展已有type需要创建新type,不可以重名。支持更复杂的类型操作
type Tuple = [number, string];
const a: Tuple = [2, 'sir'];
type Size = 'small' | 'default' | 'big' | number;
const b: Size = 24;
 
// 联合类型
interface Dog {
 wong();
}
interface Cat {
 miao();
}
 
type Pet = Dog | Cat
 
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
//还有其他的操作
type StringOrNumber = string | number; 
type Text = string | { text: string }; 
type NameLookup = Dictionary<string, Person>; 
type Callback<T> = (data: T) => void; 
type Pair<T> = [T, T]; 
type Coordinates = Pair<number>; 
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
  • interface 能够声明合并
// 接口的非函数
interface User {
 name: string
 age: number
}
 
interface User {
 sex: string
}
 
/*
User 接口为 {
 name: string
 age: number
 sex: string 
}
*/
// 函数成员,每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意,当接口 A与后来的接口 A合并时,后面的接口具有更高的优先级
interface Cloner {
    clone(animal: Animal): Animal;
}

interface Cloner {
    clone(animal: Sheep): Sheep;
}

interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}

4. 实现与继承(extends vs implements)

extends 很明显就是ES6里面的类继承。

implements实现。与C#或Java里接口的基本作用一样, TypeScript 也能够用它来明确的强制一个类去符合某种契约

interface IDeveloper {
 name: string;
 age?: number;
}

class dev implements IDeveloper { // ok
 name = 'Alex';
 age = 20;
}

class dev2 implements IDeveloper { // ok
 name = 'Alex';
}

class dev3 implements IDeveloper {
 name = 'Alex';
 age = '9'; // Error
}

而 extends 是继承父类,两者其实可以混着用

class A extends B implements C,D,E

5.声明文件与命名空间: declare 和 namespace

5.1 声明文件

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

  • declare var 声明全局变量

  • declare function 声明全局方法

  • declare class 声明全局类

  • declare enum 声明全局枚举类型

  • declare global 扩展全局变量

  • declare module 扩展模块

5.2 命名空间 (命名空间一个最明确的目的就是解决重名问题) “内部模块”现在称做“命名空间”

TypeScript 中命名空间使用 namespace 来定义,语法格式如下:

namespace SomeNameSpaceName { 
   export interface ISomeInterfaceName {      }  
   export class SomeClassName {      }  
}
如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字

要在另外一个命名空间调用语法格式为:
SomeNameSpaceName.SomeClassName;

如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:

/// <reference path = "SomeFileName.ts" />

6.访问修饰符: private 、 public 、 protected

  • 默认为 public
  • 当成员被标记为 private 时,它就不能在声明它的类的外部访问,比如:
class Animal {
    private name: string;
    constructor(theName: string) {
        this.name = theName;
    }
}

let a = new Animal('Cat').name; //错误,‘name'是私有的
  • protected 和 private 类似,但是, protected 成员在派生类中可以访问
class Animal {
     protected name: string;
     constructor(theName: string) {
     this.name = theName;
 }
}
 
class Rhino extends Animal {
     constructor() {
     super('Rhino');
     }  
     getName() {
     console.log(this.name) //此处的name就是Animal类中的name
 }
}

五.React + Ts 的结合使用

1.创建React 和 TypeScript


npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts

react-scripts-ts是一系列适配器,它利用标准的create-react-app工程管道并把TypeScript混入进来。此时的工程结构应如下所示:

my-app/
├─ .gitignore
├─ node_modules/
├─ public/
├─ src/
│  └─ ...
├─ package.json
├─ tsconfig.json
└─ tslint.json

注意: tsconfig.json包含了工程里TypeScript特定的配置选项。(可以tsc --init生成自己的TS配置文件)

  1. 添加状态管理
//安装redux 和 react-redux
npm install -S redux react-redux @types/react-redux

六.Vue + Ts 的结合使用

vue create my-app

有两种书写方式

  • 第一种: Vue.extend
export default Vue.extend({
  props: ["name", "initialEnthusiasm"],
  data() {
    return {
      enthusiasm: this.initialEnthusiasm
    };
  },
  methods: {
    increment() {
      this.enthusiasm++;
    },
    decrement() {
      if (this.enthusiasm > 1) {
        this.enthusiasm--;
      }
    }
  },
  computed: {
    exclamationMarks(): string {
      return this.enthusiasm;
    }
  }
});
  • 第二种:vue-property-decorator 和 vuex-class 提供的装饰器
vue-property-decorator 的装饰器:
  • @Prop
  • @PropSync
  • @Provide
  • @Model
  • @Watch
  • @Inject
  • @Provide
  • @Emit
  • @Component (provided byvue-class-component)
  • Mixins (the helper function named mixins provided by vue-class-component)
vuex-class 的装饰器:
  • @State
  • @Getter
  • @Action
  • @Mutation
import { Vue, Component, Prop } from "vue-property-decorator";

@Component
export default class HelloDecorator extends Vue {
    // prop属性
    @Prop() name!: string;
    @Prop() initialEnthusiasm!: number;

    enthusiasm = this.initialEnthusiasm;
    // 方法
    increment() {
        this.enthusiasm++;
    }
    decrement() {
        if (this.enthusiasm > 1) {
            this.enthusiasm--;
        }
    }
    // 计算属性
    get exclamationMarks(): number {
        return this.enthusiasm;
    }