春节间的TypeScript笔记整理


写在前面

由于在春节期间,实在没什么事干,就想系统的写份typescript笔记。废话不多说,为什么我们都在说着ts这件事呢,尤其对于前端开发者来说,JavaScript作为行为交互,是四剑客之一。那么为什么还要用ts呢?

现状分析

我们知道,目前国内主流的3大前端开发框架:

框架名称versiongithub star
ReactV16.12.0142,715
Vue.jsV2.x155,969
Angular56,653

tips:这里暂不说跨平台解决方案,比如比较优秀的Flutter等。
当然了我们会有一个疑问,倘若我们用ts编码,会不会现有的框架会不支持,这点咱们大可不必担心。

那我们想再看下,当前比较流行的框架、库,它们的源码情况

  • Redux

  • mobx

正是由于

  • 优秀的第三方框架库ts的比重越来越多
  • 即将发布的Vue.js 3x 版本,源码用ts重构
  • 老旧的项目可以无缝衔接ts

以及我们众所周知的JavaScript本身存在的一些不够完美的地方,例如xxx 未定义,等等。当然了在一些原有的项目中可能也不习惯重新接触ts,那么我和大家一块来来做做笔记,这对于我们还是很有必要的。

环境准备

  • 编辑器
    这里推荐很好用的Vscode ,本笔记相关代码也是用此

  • 相关环境版本

    • node
    • npm
    • vue/cli
  • 第三方辅助

      • ts-node@8.6.2
        TypeScript execution and REPL for node.js
    npm install -g ts-node
    

    具体使用参考
    ts-node xxx.ts

    • nodemon@2.0.2
    npm i -g nodemon
    

笔记大纲

看过我其他笔记的掘友,知道我是刚搞了台显示器,那电脑还是用的比较陈旧的不灵敏的mac,所以可能会少些xmind 图什么的,希望您也能看下去,这么无聊的文字。笔记主要分为三部分

前期思路

JavaScript vs TypeScript

首先,我们不管是读什么文章,或者茶余饭后同事唠嗑,多少都有耳闻,ts相比于js会有几点不同

  • 真正意义上的面向对象编程
  • 类型校验
  • 是js 的超集

对这几点对于编程来说还是尤为重要的,我们先看几个简单的demo
一些简单的浏览器调试,咱们选择在vscode里直接预览,这里推荐一个插件

  • Brower Previews

具体的使用方法可参考文末

  1. 声明一个变量,并赋值

就很奇怪,我们命名想让aString是一个字符串,可又能重新赋值为数字

  1. 定义一个求和功能函数
let aString = `我是一个字符串`
aString = 123
// alert(aString)
console.log(aString)
const addFn = (num1, num2) => alert(num1 + num2)
console.log(1, 2)
console.log('1', '2')
console.log('1', 2)
console.log(1)

我们可以发现,运行的时候才知道我们出现了参数的问题,那么这何尝不是我们头疼的一个问题呢,就像是

undefined.fliter()

本节小结

可以简单的体会到以下几点

  • 始于js,归于js
  • 弱类型渐渐有点强
  • 社区更新的非常快
  • 适合大型项目的开发
  • 更好尽早的避免BUG
  • 能够少些一些注释
    例如我们的一些方法,可以减少相关的文档注释
    /**
 * 
 * @param {string} name 
 * @param {number} age 
 * @param {string} sex 
 */
const Me = (name, age, sex) => `我的名字是${name},我今年${age},我是${sex}生,欢迎关注我的掘金呦~`
console.log(Me(`洋小洋同学`, 18, `男`))
=>我的名字是洋小洋同学,我今年18,我是男生,欢迎关注我的掘金呦~
  • IDE良好的提示(这里我们后续慢慢体会下)

学习文档

hello-world

老规矩,咱们一块写个hello world

const hello = (): string => `hello TS`

我们知道浏览器是不支持ts代码的(目前),那么我们该如何编辑一下呢

安装

> npm install -g typescript

+ typescript@3.7.5
这样我们就可以通过tsc xxx.ts来进行编译为js代码

var hello = function () { return "hello TS"; }; // js代码

watch 监听

这里我们可以通过tsc -w xx.ts 来监听ts文件的变化并编译

遇到问题

在我们基础调试的过程中,会遇到 was also declared here

解决

类型

这里咱们知道:JS数据类型分类和判断 JavaScript中有6种数据类型:数字(number)、字符串(string)、布尔值(boolean)、undefined、null、对象(Object)。 其中对象类型包括:数组(Array)、函数(Function)、还有两个特殊的对象:正则(RegExp)和日期(Date)。
那么在ts中,定义变量

let a: number
a = 123 // 其中a 只可以是number 类型

任意类型

ts中有一种类型,叫做任意类型any

  • 尽可能少的使用any
let list: any[] = [1, true, "free"];
list[1] = 100;

const myInfoFn = (info: any) => {
    switch (typeof info) {
        case `number`:
            console.log(`我今年${info}`)
        case `string`:
            console.log(`我的名字${info}`)
        default:
    }
}
myInfoFn(`yayxs`)

数组

在实际的开发中,经常打交道的便是数组,那么在ts中有两种常用定义数组的方式(其中数组的每项元素是同一类型)

let arrSt: string[] = [`yayxs`]  // 每一项是string
let arrNum: Array<number> = [1, 2, 3] // 每一项是number

元组

  • 元素的个数固定
  • 顺序尽量不变
let tuple: [string, number, string]
tuple = [`yayxs`, 18, `男生`]
console.log(`${tuple[0]}${tuple[2]},今年${tuple[1]}`)

函数

函数无非是参数``返回值等,其中包括默认参数,可选参数等

const addFn = (a: number, b: number): number => a + b
const addCon = (a: number, b: number): void => console.log(a+b) // 函数没有返回值
const addFn = (a: number, b: number = 10): number => a + b
// 参数默认值 b默认值值10

可选参数

const addABC = (a: number, b: number, c?: number): number => {
    if (c) {
        return a + b + c
    } else {
        return a + b
    }
}

不确定参数
口语中的不确定参数又叫剩余参数,示例:

const arrList: Array<number> = [1, 2, 3, 4];
// 其中acc为累加器;cur当前值
const reducer = (acc, cur): number => acc + cur;
let res: number = arrList.reduce(reducer, 5)
console.log(res) // 15
const addFun = (a: number, ...nums: number[]) => nums.reduce(reducer, a)
console.log(addFun(1, 2, 3, 4)) // 10

class MyGirlFriend {
    // 定义数据内容
    name: string;
    age: number;
    height: string
}
// new 对象
let xuehua = new MyGirlFriend()
// set 内容
xuehua.name = `xuehua` // 雪花
// get 内容
console.log(xuehua.name)
class MyGirlFriend {
    // 定义数据内容
    name: string;
    age: number;
    height: string
    constructor(name: string, age: number, height: string) {
        this.name = name
        this.age = age
        this.height = height
    }
    formatHeight() {
        return `${this.height} cm`
    }
}
// new 对象
let xuehua = new MyGirlFriend(`xuehua`, 20, `172`)
console.log(xuehua.name) // xuehua
console.log(xuehua.age) // 20
console.log(xuehua.formatHeight()) // 172cm

我们可以看下编译为js之后的代码


var MyGirlFriend = /** @class */ (function () {
    function MyGirlFriend(name, age, height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
    MyGirlFriend.prototype.formatHeight = function () {
        return this.height + " cm";
    };
    return MyGirlFriend;
}());
// new 对象
var xuehua = new MyGirlFriend("xuehua", 20, "172");
console.log(xuehua.name);
console.log(xuehua.age);
console.log(xuehua.formatHeight());

继承

我们都知道面向对象``继承``多态,那么什么是继承嘞:

/// 继承Demo
class Person {
    // 数据-属性
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    // 行为-方法
    sayHi() {
        console.log(`HI`)
    }
}
// 继承Person
class Programmer extends Person {
    sayNo() {
        console.log(`NO`)
    }
    sayHi() {
        // console.log(`en~~~`)
        super.sayHi() // 调用父类
    }
}
let p = new Programmer(`yayxs`, 18)
p.sayHi()

相应的js代码


var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
/// 继承Demo
var Person = /** @class */ (function () {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    // 行为-方法
    Person.prototype.sayHi = function () {
        console.log("HI");
    };
    return Person;
}());
// 继承Person
var Programmer = /** @class */ (function (_super) {
    __extends(Programmer, _super);
    function Programmer() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Programmer.prototype.sayNo = function () {
        console.log("NO");
    };
    Programmer.prototype.sayHi = function () {
        // console.log(`en~~~`)
        _super.prototype.sayHi.call(this);
    };
    return Programmer;
}(Person));
var p = new Programmer("yayxs", 18);
p.sayHi();

抽象类

抽象类不能够被实例化,但是可以被继承,通常要完成方法的实例化

// 抽象类
abstract class Person {
    name: string
    constructor(name: string) {
        this.name = name
    }
    sayHi(name: string): void {
        console.log(`hi`)
    }
    // 抽象方法 没有方法体
    abstract work(): void
}
// let p = new Person() err
class Coder extends Person {
    sayHi() {
        console.log(`12131`)
    }
    work() {
        console.log(`i am working`)
    }
}

修饰符

  • public

    • 默认就是public 修饰
    • 公有,外部也可访问
    • 继承的对象也能使用
  • private

    • 私有的,内部才能访问
  • protected

    • 受保护的,权限稍大于 private

其实在类的内部,所有的属性方法默认是通过public修饰符来进行修饰的

class Person {
    public name:string  // 默认都是公开的
}

关于ts中的修饰符在其他语言中也是类似的,例如Java等。

// 公共,私有与受保护的修饰符
class Person {
    public name: string
    private age: string
    public sayName() {
        console.log(`my name is ${this.name}`)
    }
    private sayAge() {
        console.log(`my age is ${this.age}`)
    }
}

那么通过private 修饰的方法,在外部该怎么访问到呢?

getName() {
        console.log(this.name)
    } // 获取名字
    
let p = new Person()
p.getName() // undefined
getName() {
        console.log(this.name)
    }
    setName(name: string) {
        this.name = name
    }
let p = new Person()
p.setName(`yayxs`)
p.getName() // yayxs

constructor

当被protected 修饰后,是不能new的,也就是说不能实例化

静态属性&方法

通过static 修饰的属性或者方法,可以直接通过类.xxx,

class Person {
    public static _name: string;
    public static getName(): string {
        return Person._name
    }
}
console.log(Person.getName())

本节小结

ts中类的实践还是十分有意义的,包括像静态属性方法,或者像public protected private 这些修饰符修饰在属性以及方法前边,主要是权限的限制,大致是
public > protected > private 具体的修饰细则,包括像
在父类以及子类的调用权限问题,可以翻阅相关的文档

  • 静态成员更像是存在于类的本身,而不是new出来的实例对象上,这点官网也有说到

枚举

首先不得不提的是枚举在实际开发的过程中,还是十分有益的,能够增强代码的可读性,笔者在封装网络请求类或者网页中常见的下拉选择框有用到

enum RequestType {
    GET, POST, PUT, DELETE
}
console.log(RequestType.DELETE) // 3

之于为什么是3

var RequestType;
(function (RequestType) {
    RequestType[RequestType["GET"] = 0] = "GET";
    RequestType[RequestType["POST"] = 1] = "POST";
    RequestType[RequestType["PUT"] = 2] = "PUT";
    RequestType[RequestType["DELETE"] = 3] = "DELETE";
})(RequestType || (RequestType = {}));
console.log(RequestType.DELETE);

接口

接口在实际生活中也是无处不在的,像水龙头与水管 插板与插槽 等等,那么在程序中也是一样的,同一接口规范了一类

// 接口
interface HasName {
    name: string
}
// 定义对象
const obj = {
    name: `yayxs`
}
// 定义方法
const sayName = (o: HasName) => {
    console.log(`my name is ${o.name}`)
}
sayName(obj) // my name is yayxs

以上的接口示例是只有参数,那接口里也是可以定义方法的,就像这样

// 接口
interface HasName {
    name: string
    printName(name: string): void
}
// 定义对象
const obj = {
    name: `yayxs`,
    printName: (name: string) => {
        console.log(name)
    }
}
// 定义方法
const sayName = (o: HasName) => {
    console.log(`my name is ${o.name}`)
    o.printName(o.name) // yayxs
}
sayName(obj)

可选参数

在定义接口的时候,我们可以定义一部分可选择的属性或方法,在实现接口的时候不是非要实现

/// 可选参数
type PerInfo = string
interface Person {
    name: PerInfo
    age?: number
    sex: PerInfo
    printName(): void
    printAge?(): void
}
// 我实现人类接口
class Me implements Person {
    printName(): void {
        console.log(`xxx`)
    }
    name: string
    sex: string
}
let m = new Me()

类型别名

类型别名,顾名思义就是类型的别名

// 类型别名
type Name = string; // 把string别名为Name 更语义化一点
const myName: Name = `yayxs`
console.log(myName)

又或者

type User = {
    name: string,
    age: number,
    sex: string
}
const me: User = {
    name: `yayxs`,
    age: 18,
    sex: `nan`
}
console.log(me)

那么类型别名接口有点相似

  • 类型别名不可以重复定义,而接口是可以的
  • 接口常用些

类与接口

// 定义支付接口
interface Pay {
    doSomething(): void // 支付接口方法
}
const myPay = () => {
    // 实现接口中的pay()
    doSomething: () => {
        console.log(`实现支付动作`)
    }
}
// 定义不同的类来实现接口
class WxPay implements Pay {
    doSomething() {
        console.log(`我是微信支付`)
    }
}
class AlPay implements Pay {
    doSomething() {
        console.log(`我是支付宝支付`)
    }
}
let weixin_pay: Pay = new WxPay()
let ali_pay: Pay = new AlPay()

匿名函数接口

在一些接口的内部声明的函数是没有名字的

// 匿名函数接口
interface Person {
    (num: number): void
}
let printNum: Person;
printNum = (num: number) => {
    console.log(num)
}

类型断言

包括像是在dart语言内,都会有断言存在

  • 主要是在编译时期
  • 告诉编译器是什么类型
// 类型断言
let x: any = `hello`
let res = (<string>x).substring(0, 1)
console.log(res)

在实际开发中书写断言几种常见的方式

interface Person {
    name: string
    age: number
}
let me = {} as Person
me.name = `yayxs`
me.age = 18
let you = <Person>{
    name: `xuehua`,
    age: 17
}

接口继承

类不能继承多个父类,但是可以实现多个已定义的接口

interface Person {
    name: string
}
interface Coder {
    age: number
}
class Per implements Person, Coder {
    name: string
    age: number
} 

可索引类型

// 可索引类型
interface Istr {
    [index: string]: string
}
let myStr: Istr;
myStr = {
    'name': `yayxs`
}
interface Inum {
    [index: number]: string
}
let myNum: Inum
myNum = [`12`]

在如上第二个例子中,有点像数组的定义方式,那与数组有什么不同呢

  • 可索引类型没有数组的相关方法和属性length或者push等常见的方法

类型“Inum”上不存在属性“length”。

getter && setter

在其他的编程语言中,获取属性与设置也同样的有这种情况
在前面部分我们提到过

这样的话,就遇到一个问题,无法读取name属性,这时候可以通过内部方法暴露出来,那同样在类中有get set

// setter and getter
class Person {
    private name: string
    private age: number
    constructor(age: number, name: string) {
        this.name = name
        this.age = age
    }
    get getName(): string {
        return this.name
    }
    set setName(name: string) {
        this.name = name
    }
}
let p = new Person(18, `yayxs`)
// console.log(p.name)

函数

函数重载

js中是没有函数重载的,那么在ts中什么是函数重载呢,

  • 意思就是先定义方法,但不实现方法体
  • 函数的名称可以重名,并不会被覆盖
// 函数重载
function hello(name: string): string
function hello(age: number): number
function hello(params: any): any {
    if (typeof params === `string`) {
        return params
    } else if (params === `number`) {
        return params
    }
}

非空检查

我们可以通过!来进行非空的检查


let str: string = `yayxs`;
str!.substring(0, 1)

never

  • 一种情况是,不期待有返回结果

function loop():never{
    while(true){
        
    }
}
  • 一种是内部抛出异常
function err(): never {
    throw Error
}

泛型

在其他的语言中像JAVA中有泛型的概念,

// 泛型
function getArr<T>(params: T[]): T[] {
    return params
}
console.log(getArr<number>([22, 1]))
class Person<S, N> {
    private _name: S
    private _age: N
    constructor(name: S, age: N) {
        this._name = name
        this._age = age
    }
    get name(): S {
        return this._name
    }
    get age(): N {
        return this._age
    }
}
let p = new Person<string, number>(`yaxs`, 18)
console.log(p.name)

在编译的时候请使用

tsc --target es5 demo12.ts

模块化

可以通过两种方式导出ts中的元素

  • module default export
  • export

tsconfig.json

可以通过tsc --init来生成这么一个文件

{
  "compilerOptions": {
    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */
    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
    /* Advanced Options */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

具体的注释的含义,可以参考官方文档

倾情链接

如果你还没有看过瘾,在19年度我曾写过一篇很简单的文章,也是基于第三方的环境搭建的一篇极为简单的博客(依赖github、Vuepress)

写在最后

相关代码已经上传至 笔者github ,也欢迎指正不恰当的地方,感谢~

参考文章

https://juejin.im/post/5e377a5de51d4530e60e4d1d

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论