TypeScript 快速入门

2020-08-06 23:51:24 浏览数 (1)

❝快速入门TypeScript,理解类型系统以及JavaScript自有类型系统的问题 ❞

理解类型系统

理解JavaScript自有类型系统的问题。先要理解以下的类型系统

  • 强类型与弱类型(类型安全)

强类型:语言层面限制函数的实参类型必须与形参类型相同

代码语言:javascript复制
class Main{
	static void foo(int num){
		System.out.println(num);
	}
	//Main.foo(100)//正确的
	//Main.foo("100")//错误的
}

弱类型:语言层面上不会限制实参的类型

代码语言:javascript复制
function foo(num){
	console.log(num)
}
//语法上不会报错 可以传入任意类型
foo(100);//ok
foo("100");//ok

由于这种强弱类型之分根本不是某一个权威机构的定义,一般描述强类型有更强的类型约束,而弱类型中几乎没有什么约束。

强类型语言中不允许任意的隐式类型转换,而弱类型语言则允许任意的数据隐式类型转换

变量类型允许随时改变的特点,不是强弱类型的差异

  • 静态类型与动态类型(类型检查)

静态类型:一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许再修改了

动态类型:在运行阶段才能够明确变量类型,而且变量的类型随时可以变化

  • JavaScript自有类型系统的问题

JavaScript 是弱类型且动态类型的语言 【任性】缺失了类型系统的可靠性【不靠谱】

早期JavaScript应用简单,JavaScript是一个脚本语言,没有编译环节的(静态类型语言需要编译 检查的)在大规模应用下,JavaScript的灵活多变的优势,变成了短板。

弱类型的问题:

在小项目中,我们可以通过约定的方式进行设置,但是在大规模项目这种约定就会存在很大的隐患

代码语言:javascript复制
//JavaScript 弱类型产生的问题
const obj = {}

//运行到此就会报错 抛出异常 这是一个隐患 而强类型的语言直接在语法层面上抛出错误
setTimeout(() => {
    obj.foo();
}, 1000);

//如下 传递不同类型的参数 函数的作用就完全不同了 如果使用强类型的语言 会直接限制参数的类型
function sum(a,b){
   return a b;
}

sum(100,100);//200
sum(100,"100");//100100

//对象属性名会自动转换为字符串
const obj = {};
obj[true] = 100;
console.log(obj['true']);//对对象索引器的错误用法

强类型的优势:

  • 错误更早暴露
  • 代码更智能,编码更准确
  • 重构更牢靠
  • 减少不必要的类型判断

Flow

Flow 静态类型检查方案

yarn add flow-bin --dev 安装flow

对代码添加标记:// @flow

yarn flow init 初始化flow

yarn flow 执行flow命令检测代码中的类型问题

代码语言:javascript复制
/*
@flow
*/
function s(num:number):void{
    console.log(num);
}
s(123);

JavaScript 没有编译阶段,直接运行的,而Flow就是给JavaScrip增加了编译阶段来检查类型错误

  • flow 编译移除注解

一般注解类型只是在开发阶段使用,在线上环境是不需要的那么在线上环境的时候需要移除注解,

安装模块:yarn add flow-remove-types --dev

执行命令 :yarn flow-remove-types src -d dist

就会把类型注解去掉生成的代码如下,dist目录下的代码就可以提供到线上使用

代码语言:javascript复制
function sum(a:number,b:number){
 return a   b;
}

//flow 编译之后的代码 移除类型注解
function sum(a        , b        ) {
    return a   b;
}

sum(100,100);
sum('100','100');
  • babel

yarn add @babel/core @babel/cli @babel/preset-flow --dev 安装babel模块来实现编译移除注解类型

配置.babelrc 文件

代码语言:javascript复制
{
    "presets": ["@babel/preset-flow"]
}

然后执行:yarn babel src -d dist 将src中的源代码移除类型注解,放到dist目录下

  • flow 开发工具插件

Flow language support 插件vscode 支持检测类型错误 而不用再去运行命令:yarn flow

  • 原始类型
代码语言:javascript复制
/*
    原始类型
    @flow
*/
const a:string = 'foobar';

const b:number = 100;

const b1:number = NaN;

const b2:number = Infinity;

const c:boolean = true;

const d:null = null;

const e:void = undefined;

const f:symbol = Symbol();

function s():void{
    
}

function ss():number{
    return 100;
}
  • 数组类型
代码语言:javascript复制
/* 
数组类型
@flow
*/
const arr:Array<number> = [1,2,3];//<T> 泛型

const arr2:number[] = [1,2,3,4];

//固定长度的数组
const foo:[string,number] = ['foo',123];//第一个元素必须是字符串 第二个元素是数字
  • 对象类型
代码语言:javascript复制
/* 
对象类型
@flow
*/
//定义对象的成员类型方式如下
const obj:{foo:string,bar:number} = {
    foo:'string',bar : 123
}

//上述定义成员类型,成员必须定义否则报错 可以通过?:的方式 成员不是必须定义的
const obj1:{foo?:string,bar:number} = {
    bar:123
}

//设置对象属性键的类型限制和值的类型限制
const obj2:{[string]:string}={}

obj2.key='value';
obj2.age= '14';
  • 函数类型
代码语言:javascript复制
/* 
函数类型
@flow
*/
//设置函数的参数类型 以及返回值类型
function s(num:number):void{
    console.log(num);
}
s(123);

function ss():number{
    return 100;
}

//回调函数的类型限制(string,number)=>void 参数类型以及返回值类型
function foo(callback:(string,number)=>void){
    callback('zce',123);
}

foo(function(name,num){
    console.log(name,num);
});
  • 特殊类型
代码语言:javascript复制
/* 
特殊类型
@flow
*/
//字面量类型 a 只能等于foo
const a:'foo'='foo';

//type 可以等于'success' | 'warning' | 'danger'
const type:'success' | 'warning' | 'danger' = 'success';

//同时还可以使用多个类型
type StringOrNumber = string | number;

const b : StringOrNumber = '12';//100

const gender:?number = 100;//? maybe类型 可以接受undefined null
  • 任意类型
代码语言:javascript复制
/* 
任意类型
@flow
*/

//mixed 强类型 就可以传递任意的类型
function passMixed(value:mixed){
    //需要添加类型判断
    if (typeof value === 'number') {
        value * value;
    }
    if (typeof value === 'string') {
        value.substr(1);
    }
    
}
passMixed(200);
passMixed('100');

//any 弱类型 也可以表示任意类型 一般不推荐使用any
function passAny(value:any){
    //any可以使用任何方法 value 其实还是弱类型的
    value * value;

    value.substr(1);
}
passAny(200);
passAny('100');
  • 运行环境API 类型
代码语言:javascript复制
/* 
运行环境API 类型
https://github.com/facebook/flow/blob/master/lib/dom.js
@flow
*/
//DOM BOM Node的内置API等 都有一些类型限制
const element:HTMLElement | null =  document.getElementById("id");

以上就是Flow提供的静态类型检查方案,其实TypeScript写法与其类似。

TypeScript

TypeScript解决JavaScript类型系统的问题,TypeScript大大提高代码的可靠程度

TypeScript 可以在任何一个JavaScript中的运行环境中都支持

缺点一:语言本身多了很多概念

缺点二:项目初期,会增加一些成本

TypeScript 属于渐进式的

TypeScript 最终会编译成JavaScript并且支持到ES3的标准

yarn add typescript --dev 项目中添加typescript模块

yarn tsc 01-getting-start.ts 编译ts文件 ,tsc命令就是编译ts文件

代码语言:javascript复制
/* 
    可以完全按照javascript进行编码
*/
const hello = (name:string) => {
    console.log(`hello ${name}`);
}
hello('zce');

编译后的文件如下:

代码语言:javascript复制
"use strict";
/*
    可以完全按照javascript进行编码
*/
var hello = function (name) {
    console.log("hello "   name);
};
hello('zce');
//# sourceMappingURL=01-getting-start.js.map
  • TypeScript 配置文件

编译整个项目,创建配置文件:yarn tsc --init

配置文件:TS 还有很多配置 可以自行去官网查阅 下面的配置是常用的配置方式

代码语言:javascript复制
"target": "es5",//编译后的标准js代码
"module": "commonjs",//模块化
"outDir": "dist",//编译后的目录
"rootDir": "src",//源代码目录
"sourceMap": true,//源代码映射
"strict": true,//开启所有严格检查选项
"lib": ["ES2015","DOM"],//配置标准库 如果要使用ES2015的新的标准就需要引入对应的标准库包括DOM BOM等

命令:yarn tsc 编译整个项目,编译后的js文件就会存放到dist目录中

  • 原始类型在TS中的应用
代码语言:javascript复制
/* 原始类型在TS中的应用 */

const a:string = 'foo';

const b:number = 100;//NaN Infinity

const c:boolean = true;//false

// const d:string = null;//严格模式下 不允许为null

const e:void = undefined;//严格模式下 不允许为null

const f:null = null;

const g:undefined = undefined;

//如果使用ES5标准库,而这时使用ES6的标准库新的类型会出现错误。
//解决方案一:改为ES2015标准库;
//解决方案二:"lib": ["ES2015","DOM"]
const h:symbol = Symbol();

Array;

Promise;

//DOM API需要在lib中引入DOM
console.log();

//标准库就是内置对象所对应的声明
  • Object类型
代码语言:javascript复制
export{}

const foo:object = function(){}//[] {} 可以接受对象 数组 函数

//限制对象成员类型 对象的成员
const obj:{foo:number,bar:string} = {foo:123,bar:"string"};
  • 数组类型
代码语言:javascript复制
const arr1:Array<number> = [1,2,3,];

const arr2:number[] = [1,2,3,];

//---------累加
function sum(...args:number[]){
    //如果使用js就要判断参数是否为Number类型
    //ts只需要添加一个数字的类型注解即可
    return args.reduce((prev,current)=>prev   current,0);
}

// sum(1,2,3,4,'foo');
  • 元组类型

元组:就是一个明确元素数量以及元素类型的一个类型 各个元素的类型不必要完全相同

代码语言:javascript复制
const tuple:[number,string] = [18,''];//类型不相符 或者超出元素的数量都会报错

const [age,name] = tuple;

//元组类型现在非常常见了
//比如 entries 得到一个键值数组,键值数组就是一个元组[string,number]
//注意entries 属于ES2017的一个标准库
Object.entries({
    foo:123,
    bar:456
});
  • 枚举类型

一般定义类型的写法:

代码语言:javascript复制
const PostStatus={
    Draft:0,
    Unpublished:1,
    Published:2
}

枚举类型的写法

代码语言:javascript复制
//默认值是:0开始 依次累加 可以不用指定值
//枚举值可以是字符串 但是字符串无法像数字一样自增长 需要给每一个枚举赋值
//常量枚举 以及 基本枚举的编译情况是不同的 注意
const enum PostStatus{
    Draft,//0
    Unpublished,//1
    Published//2
}

const post={
    title:"Hello TypeScript",
    content:"",
    status:PostStatus.Draft//0 1
}

// PostStatus[0]// => Draft
  • 任意类型
代码语言:javascript复制
function stringify(value:any){
    return JSON.stringify(value);
}

stringify('100');

stringify(100);
//any 动态类型 ts不会对他进行类型检查
let foo:any = 'string';

foo = 100;

foo.bar();
  • 函数类型
代码语言:javascript复制
//可选参数或者默认参数 一定在参数列表的最后
function func1(a: number, b?: number,...rest:number[]): string {
  return "func1";
}

func1(100,200);
func1(100);
// func1();

函数表达式,回调函数的约束在TS中可以这样定义:(a:number,b:number) => string

代码语言:javascript复制
//函数表达式
//回调函数约束
const func2 : (a:number,b:number) => string =  function(a:number,b:number):string{
    return 'func2';
}
  • 隐式类型推断

TypeScript可以自动推荐类型,一旦确定类型就不允许改变类型

代码语言:javascript复制
let age = 18;//推断为了 number

// age = 'number';//报错

let foo;//没有赋值就是any类型
//可以给任意类型的值 语法上不会报错
foo = 100;

foo = 'string';

//建议每个变量添加更直观的类型
  • 类型断言
代码语言:javascript复制
const nums = [100,200,199,112];

const res = nums.find(i=>i>1000);//推断res可能找不到

// const square = res * res;
const num1 = res as number;//告诉编译器 我一定是number 不用担心

const num2 = <number>res;
  • 接口

接口 可以约定一个对象的结构,可以约定有哪些成员 TS 只是进行了约束 在编译成JavaScript时实际上没有任何意义和普通对象一样

代码语言:javascript复制
interface Post {
  title: string;
  content: string;
  //可选成员
  subtitle?: string;
  //只读成员
  readonly summary:string;
}

function printPost(post:Post){
    console.log(post.title);
    console.log(post.content);
}
//subtitle是可选属性 可以不用传入
printPost({title:'hello',content:'a javascript',summary:'12'});

const hello: Post = {
  title: "hello",
  content: "a javascript",
  summary:'A JavaScript'
};

hello.summary = "123";//报错 因为summary属性是一个只读属性 不能修改属性值

定义动态成员的key类型 以及值的类型 定义的成员必须一致否则会报错

代码语言:javascript复制
interface Cache{
    [prop:string]:string //[prop:string] -> key的类型 :string -> 值的类型
}

const cache:Cache = {}
cache.foo = "value";
// cache.bar = 123;//报错
  • 类 Class

类 描述一类事物的抽象特征 ES6以前通过 函数 原型来模拟的类

class 在ES6中就添加了这一个特性,而TypeScript在ES6的基础上对class添加了访问修饰符,类的属性必须要先声明属性并且必须有一个初始值。

代码语言:javascript复制
class Person{
    //类的属性必须有初始值 或者构造函数中赋值否则会报错
    public name:string;//默认public 公有属性
    private age:number;//private 私有属性只能在类内部访问
    protected readonly gender:boolean;//protected 保护 只有子类可以访问
    //readonly 只读属性 通过=或者构造函数初始化就不允许再被修改了
    constructor(name:string ,age:number){
        //直接使用this.name 会报错 TS要求明确声明属性 声明的属性必须有初始值可以在=后面赋值,或者在构造函数中对他赋值
        this.name = name;
        this.age = age;
        this.gender = true;
    }
    sayHi(msg:string){
        // this.gender = false;
        console.log(`I am ${this.name} is age:${this.age}`);
    }
    logSay(){
        this.sayHi('你好啊');
    }
}
class Student extends Person{
    //构造函数声明了private 外部就不允许尽心实例化类了
    private constructor(name:string,age:number){
        super(name,age);
        console.log(this.gender);
        //
        // this.gender = false;
    }
    //可以通过内部new实例返回交给外部调用
    static create(name:string,age:number){
        return new Student(name,age);
    }
}

const tom = new Person('tom',18);
console.log(tom.name);
// console.log(tom.gender);
// console.log(tom.age);

// const jake = new Student('jake',18);
const jake = Student.create('jake',18);

类实现接口,代码如下:

代码语言:javascript复制
interface Eat{
    eat(foo:string):void
}
interface Run{
    run(distance:number):void
}

class Person implements Eat,Run{
    eat(foo:string):void{
        console.log(`优雅的进餐${foo}`);
    }
    run(distance:number):void{
        console.log(`直立行走${distance}`);
    }
}

class Animal implements Eat,Run{
    eat(foo:string):void{
        console.log(`咕噜咕噜的吃${foo}`);
    }
    run(distance:number):void{
        console.log(`爬行${distance}`);
    }
}

抽象类 可以包含一些方法的具体的实现

代码语言:javascript复制
abstract class Anima{
    eat(foo:string):void{
        console.log(`吃${foo}`);
        
    }
    //抽象方法
    abstract run(d:number):void;
}

class Dog extends Anima{
    //实现父类的抽象方法
    run(d: number): void {
        console.log(`走:${d}`);
    }
}
  • 泛型

TypeScript还引入了泛型的概念

代码语言:javascript复制
function crateNumberArray(len:number,value:number):number[]{
    //<number> 存放number类型的数据
    const arr = new Array<number>(len).fill(value);
    return arr;
}

const res = crateNumberArray(3,100);
//res => [100,100,100]

//创建string类型的数据
function crateStringArray(len:number,value:string):string[]{
    //<number> 存放number类型的数据
    const arr = new Array<string>(len).fill(value);
    return arr;
}

//通过泛型解决上述代码中的冗余部分

function createArray<T>(len:number,value:T):T[]{
    const arr = new Array<T>(len).fill(value);
    return arr;
}

createArray<number>(1,100);
createArray<string>(10,'10');

TS 与 JS混合开发,涉及到老代码等逻辑,这里就需要用到类型声明,比如一般我们使用的第三方模块 没有用ts实现这时候就需要 使用模块的类型声明

比如lodash的第三方库,提供了类型声明文件,只需要安装即可 npm install @types/lodash

代码语言:javascript复制
import {camelCase} from 'lodash';

//query-string 模块里面已经包含了类型声明的模块
import qs from 'query-string';

// qs.parse();

//声明函数的类型 明确的声明函数的类型
// declare function camelCase (input:string) : string;

//npm install @types/lodash 或者安装类型声明模块

const r = camelCase('hello worle');

0 人点赞