typescript 学习总结

简介

typescript:javasscript的超集 ,添加了类型系统的 JavaScript,适用于任何规模的项目。
我们都知道JavaScript是一种弱类型的语言。而TypeScript增强了它的类型。
TypeScript 的[核心设计理念]:在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。

TypeScript JavaScript
JavaScript 的超集用于解决大型项目的代码复杂性 一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误 是静态类型 作为一种解释型语言,只能在运行时发现错误,是动态类型
强类型,支持静态和动态类型 弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用
支持模块、泛型和接口 不支持模块,泛型或接口
社区的支持仍在增长,而且还不是很大 大量的社区支持以及大量文档和解决问题的支持

动态类型是指在运行时才会进行类型检查
静态类型是指编译阶段就能确定每个变量的类型

1. 安装typescript

npm install -g typescript

编译ts文件(生成对应的js文件,)

tsc  xxx.ts

[官方提供的在线编写typescript网址]

2. 数据类型

原始数据类型:Boolean , String , Number , null , undefined ,Symbol
其他数据类型: Arrsy , Enum , Any , Never , Object , Void , Unknow , Tuple

  • Symbol
    es6中新增的一个类型,表示独一无二的值.Symbol 值通过Symbol()函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let a  = Symbol()
let b = Symbol('b')
let d = Symbol()
a == b // false
  • unknow ,any
    在ts中any被成为全局超级类型,可以逃避类型检测
    unknow也是另一种超级类型,但是unknow类型只能被赋值 unknow类型 和any类型
let value:unknown ;
let value2: any =value 
let value3: boolean = value // Type 'unknown' is not assignable to type 'boolean
  • tuple
    一般数组是由同种类型的值组成,但是元组可以满足数组中含有不同类型的要求,
let data:[boolean , number , string] = [true ,123,'111']

3. 类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。

let c = 12345;
console.log(typeof c) // number
c = 'hahahha' // Error: Type 'string' is not assignable to type 'number'

4. 联合类型

取值可以为多种类型中的一种

let a :number | string | boolean;
a = 4
a = 'www'
a = true

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

5. class

虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 CatDog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
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");

那么成员属性与静态属性,成员方法与静态方法有什么区别呢?可以直接看一下编译生成的 ES5 代码:

var Greeter = (function () {
    // 构造函数 - 执行初始化操作
    function Greeter(message) {
        this.greeting = message;
    }
    // 静态方法
    Greeter.getClassName = function () {
        return "Class name is Greeter";
    };
    // 成员方法
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    // 静态属性
    Greeter.cname = "Greeter";
    return Greeter;
}());
var greeter = new Greeter("world");
greeter.getClassName() // Property 'getClassName' does not exist on type 'Greeter'. Did you mean to access the static member 'Greeter.getClassName' instead?
Greeter.getClassName() // Class name is Greeter

私有字段

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

let semlinker = new Person("Semlinker");

semlinker.#name;
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:
私有字段以 # 字符开头,有时我们称之为私有名称;
每个私有字段名称都唯一地限定于其包含的类;
不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
私有字段不能在包含的类之外访问,甚至不能被检测到。

抽象类
使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法:

abstract class Person {
  constructor(public name: string){}

  abstract say(words: string) :void;
}

// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error

抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。

abstract class Person {
  constructor(public name: string){}

  // 抽象方法
  abstract say(words: string) :void;
}

class Developer extends Person {
  constructor(name: string) {
    super(name);
  }
  
  say(words: string): void {
    console.log(`${this.name} says ${words}`);
  }
}

const lolo = new Developer("lolo");
lolo.say("I love ts!"); // lolo says I love ts!

6. implements / extends

  • implements
    实现, 一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能
  • extends
    继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法
interface Test {
    x:number;
    y:number
}

class TestDemo implements Test{
    x = 1; 
    y = 2;
    sayHello(){
        console.log(`${this.x}--${this.y}say hello`)
    }
}

class Test2Demo extends TestDemo{
    y = 4 
    sayHello(){
        console.log(`${this.x}--${this.y}say hello 2`) 
    }
} 

const a = new Test2Demo()
const b = new TestDemo()
a.sayHello();
b.sayHello()
//1--4say hello 2
//1--2say hello
  • 只有类才能实现和继承类
  • 可以多实现和多继承

7. 泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

function createArray(length: number, value: any): Array {
    let result = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

无法精准定位返回的数据类型,any可以是任意类型

function createArray(length: number, value: T): Array {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']

这样可以精准推算出返回的数据类型

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。

  • 泛型接口
    当然也可以使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {
    (length: number, value: T): Array;
}

let createArray: CreateArrayFunc;
createArray = function(length: number, value: T): Array {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']
  • 泛型类
class GenericNumber {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};
  • 泛型约束
    在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:所有可以对泛型进行约束,必须含有特定的属性
function loggingIdentity(arg: T): T {
    console.log(arg.length);
    console.log(arg.personName);
    return arg
}

loggingIdentity({personName:'hahah' ,length:4}) // 4 , 'hahah'
  • 在泛型约束中使用类型参数
    可以声明受另一个类型参数约束的类型参数。
function getProperty(obj: T, key: K) {
  return obj[key];
}
 
let x = { a: 1, b: 2, c: 3, d: 4 };
 
getProperty(x, "a"); 1
getProperty(x, "m"); //Error Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

8. 装饰器

[参考]

8.1 装饰器是什么
  • 它是一个表达式
  • 该表达式被执行后,返回一个函数
  • 函数的入参分别为 target、name 和 descriptor
  • 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象
8.2 装饰器的分类
  • 类装饰器(Class decorators)
  • 属性装饰器(Property decorators)
  • 方法装饰器(Method decorators)
  • 参数装饰器(Parameter decorators)

需要注意的是,若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

Class decorators

// Class decorators
function classDecorator(constructor: T){
    return class extends constructor{
        newProperty ='new property';
        hello = 'override';
    }
}

function classFactoryDecorator(num:number){
   return function(constructor:Function){
       constructor.prototype.luckyNumber = Math.floor(Math.random() * num) 
   }
}

@classDecorator
@classFactoryDecorator(20)
class TestClass {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

let greeting = new TestClass('hah');

console.log(greeting.hello , greeting.property , greeting.luckyNumber)
// "override",  "property",  9 

Property decorators

// Property decorators

function Min(num:number){
   return function(target:Object , properrtyKey:string){
       let value:string;
       const setter = function(newVal:string){
           if(newVal.length < num){
            throw new Error(`Your password should be bigger than ${num}`)
           }
          value = newVal
       }

       const getter = function(){
           return value
       }
      Object.defineProperty(target , properrtyKey , {
         get:getter,
         set:setter
      })
   }
}


class Password{
    @Min(4)
    password:string;

    constructor(password:string){
      this.password = password
    }
}


let password = new Password('121212')
console.log(password.password)
let password2 = new Password('12') // Error :Your password should be bigger than 4 

Method decorators

// declare type MethodDecorator = (target:Object, propertyKey: string | symbol,      
//     descriptor: TypePropertyDescript) => TypedPropertyDescriptor | void;


function logger(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
   console.log(`${propertyKey}is working`)
}


function doubleResult(){
    return function(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
        return {
            value: function(...args:any[]){
                var result = descriptor.value.apply(this, args) * 2;
                return result;
            }
        }
     }
}


class MethodDecoratorClass {
    newName:string ;
    constructor(newName:string){
        this.newName = newName;
    }

    @logger
    changeName(name:string){
        this.newName = name
    }

    @doubleResult()
    sum(x:number , y:number){
        return x + y
    }
}

let demo = new MethodDecoratorClass('hello');
demo.changeName('hahahh')
console.log(demo.newName)

console.log(demo.sum(1 ,2))

// [LOG]: "changeNameis working" 
// [LOG]: "hahahh" 
// [LOG]: 6 

Parameter decorators

function notNull(target: any, propertyKey: string, parameterIndex: number) {
    console.log("param decorator notNull function invoked ");
    Validator.registerNotNull(target, propertyKey, parameterIndex);
}

function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("method decorator validate function invoked ");
    let originalMethod = descriptor.value;
    //wrapping the original method
    descriptor.value = function (...args: any[]) {//wrapper function
        if (!Validator.performValidation(target, propertyKey, args)) {
            console.log("validation failed, method call aborted: " + propertyKey);
            return;
        }
        let result = originalMethod.apply(this, args);
        return result;
    }
}
class Validator {
    private static notNullValidatorMap: Map> = new Map();

    //todo add more validator maps
    static registerNotNull(target: any, methodName: string, paramIndex: number): void {
        let paramMap: Map = this.notNullValidatorMap.get(target);
        if (!paramMap) {
            paramMap = new Map();
            this.notNullValidatorMap.set(target, paramMap);
        }
        let paramIndexes: number[] = paramMap.get(methodName);
        if (!paramIndexes) {
            paramIndexes = [];
            paramMap.set(methodName, paramIndexes);
        }
        paramIndexes.push(paramIndex);
    }
        static performValidation(target: any, methodName: string, paramValues: any[]): boolean {
        let notNullMethodMap: Map = this.notNullValidatorMap.get(target);
        if (!notNullMethodMap) {
            return true;
        }
        let paramIndexes: number[] = notNullMethodMap.get(methodName);
        if (!paramIndexes) {
            return true;
        }
        let hasErrors: boolean = false;
        for (const [index, paramValue] of paramValues.entries()) {
            if (paramIndexes.indexOf(index) != -1) {
                if (!paramValue) {
                    console.error("method param at index " + index + " cannot be null");
                    hasErrors = true;
                }
            }
        }
        return !hasErrors;
    }
}

class Task {
    @validate
    run(@notNull name: string): void {
        console.log("running task, name: " + name);
    }
}

console.log("-- creating instance --");
let task: Task = new Task();
console.log("-- calling Task#run(null) --");
//task.run(null);
console.log("----------------");
console.log("-- calling Task#run('test') --");
task.run("test");

// [LOG]: "param decorator notNull function invoked " 
// [LOG]: "method decorator validate function invoked " 
// [LOG]: "-- creating instance --" 
// [LOG]: "-- calling Task#run(null) --" 
// [LOG]: "----------------" 
// [LOG]: "-- calling Task#run('test') --" 
// [LOG]: "running task, name: test" 
9.namespace

随着方法属性的增多,以便于在记录它们类型的同时还不用担心与其它对象产生命名冲突。 因此,我们把属性和方法包裹到一个命名空间内,而不是把它们放在全局命名空间下。

namespace Test {
   export interface TestInterface{
      location?:string
   }
   export const personName:string = 'hahahah';
   
   export  const getPersonName = () => {
      return personName
   }
}

let test:Test.TestInterface;
let nameNew = Test.getPersonName();
console.log(nameNew) // 'hahahah'

9.JSX

JSX是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript,尽管转换的语义是依据不同的实现而定的。 JSX因React框架而流行,但也存在其它的实现。 TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。

想要使用JSX必须做两件事:

  1. 给文件一个.tsx扩展名
  2. 启用jsx选项

TypeScript具有三种JSX模式:preservereactreact-native。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在preserve模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有.jsx扩展名。 react模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.jsreact-native相当于preserve,它也保留了所有的JSX,但是输出文件的扩展名是.js

你可能感兴趣的:(typescript 学习总结)