TypeScript:从零开始自我总结

418 阅读13分钟

一:TypeScript是什么 ?

  1. TypeScript 是微软开发的开源编程语言, 它是JavaScript的一个超集,简称 TS ,本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
  2. TypeScript 起源于使用JavaScript开发的大型项目 。由于JavaScript语言本身的局限性,难以胜任和维护大型项目开发。因此微软开发了TypeScript ,使得其能够胜任开发大型项目。
  3. 在JS基础上, 为JS添加了类型支持。TypeScript = Type + JavaScript
  4. JS和TS的区别:js属于动态类型的编程语言,TS属于静态类型的编程语言

ts1.jpeg

二: 安装和使用

  1. 全局安装TypeScript工具包
npm install -g typescript

2.检测TypeScript的版本

tsc -v

3.编译TypeScript文件

tsc xxx.ts

小结: 要运行.ts文件,先安装全局TypeScript工具包,该工具包提供tsc命令,通过tsc -v检测工具包的版本,tsc xxx.ts将.ts文件转化为js文件

三:TS的初体验

Ts的类型

JS已有的类型(Ts也包括):

  1. 原始类型:number string boolean null undefined symbol
  2. 对象类型:object (包括 数组 对象 函数 等...) TS新增类型:
  3. 联合类型
  4. 自定义类型(类型别名)
  5. 接口
  6. tuple(元组类型)
  7. 字面量类型
  8. enum(枚举类型)
  9. void
  10. any(任意类型)
  11. unkown

1.原始类型

//number类型
let count: number = 10;

//string 类型
let name :string = '我是小华'

//boolean 类型
let isLoading: boolean = false

//undefined 类型
let un: undefined = undefined

// null
let timer:null = null

// symbol
let uniKey:symbol = Symbol()

2.联合类型

let timer:null | number = null // 联合类型
timer = setInterval(()=>{},1000) // 定时器的返回值是一个number类型

3.类型别名

作用-> 简化代码 --- 给类型起别名 ---定义新类型

// 类型别名 *** //偷懒用的 
 type s = string
 const str:s ='abc'

 type NewType = string |number
 const arr :newType = 'abc'
 const arr1 :newType = 12

别名可以是任意的合法字符串,一般首字母大写

4.数组类型

// 第一种 简单
//  let arr2 :string[] = ['a','b',100]  //100报错

let arr2 :string[] = ['a','b']   

//  第二种  相对复杂
let arr3 : Array<string> = ['a','bc']

// 定义一个数组元素可能是字符串类型 也可能是数值类型
type newArrayType = string | number

let arr4 : Array<newArrayType> = ['a','b',100]

let arr5 : newArrayType[] = ['a','b',123]

5.函数类型

// 普通函数
function add(a:number=100, b:number=200){
    return a+b //类型推论出返回值的类型
}
function add1(a:number=100,b:number=200):number{
//小括弧后面的类型规定的是返回值的类型
    return a+b
}

// 箭头函数
const add2 =(a:number=100,b:number=200):number=>{
    return a+b
}

// 批量定义同一个函数
 type fn = (a:number,b:number)=>number
 const add3 :fn=(a,b)=>{
     return a+b
 }
 const sub :fn =(a,b)=>{ 
 //(a,b)里的形参应该要与fn自定义类型中的参数数量和类型保持一致
     return a-b
 }
 
 // 可选参数和必选参数
function axx(a:number,b?:number){
    console.log(a,b);
}
 axx(1)
 
//  function zzx(a:number=12,b?:number=11){
//**b?:number=11** 可选参数不能和默认值一起使用
  ---可选参数后不可再放必选参数
//      console.log(a,b);
//  }

函数返回值类型void

// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}

// 如果return之后什么都不写,此时,add 函数的返回值类型为: void
const add = () => { return }

const add = (): void => {
 // 此处,返回的 undefined 是 JS 中的一个值
 return undefined
}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}

void和undefined的区别

function add(a:number, b:number): undefined { // 这里会报错
 console.log(a,b)
}

如果函数没有指定返回值,调用结束之后,值是undefined的,但是不能直接声明返回值是undefined

TS函数类型区别于JS函数的一点就是 TS具有函数重载

函数重载是使用相同名称和不同参数数量或类型创建多个方法

let obj: any = {};
function ppl(val: string): void;
function ppl(val: number): void;
function ppl(val: any): void {
  if (typeof val === "string") {
    obj.name = val;
  } else {
    obj.age = val;
  }
}
ppl("hahaha");
ppl(9);
console.log(obj);

注意:

  1. 当 TypeScript 编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面,最后函数实现时,需要使用 |操作符或者?操作符,把所有可能的输入类型全部包含进去,用于具体实现。
  2. 函数重载真正执行的是同名函数最后定义的函数体 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法 只能定义类型

6.对象类型

// 对象类型
const s:{
    name:string
    hello():void //普通函数
    run:()=>void // 箭头函数
}={
    name:'华',
    hello:function () {
        console.log('nihao');
    },
    run:()=>{
        console.log('跑');
    }
}

配合别名使用 ---简化

// 简化
type Stu={
    name:string
    sex:string
    score:number
    height:number
    study():void
    play:()=>void
}

 const stu1:Stu={
     name:'阿斯顿',
     sex:'男',
     score:99,
     height:175,
     study:function(){
         console.log('我在学习');
     },
     play:()=>{
         console.log('我在打游戏');
     }
 }
 const stu2:Stu={
     name:'李世',
     sex:'男',
     score:89,
     height:171,
     study:function(){
         console.log('我没在学习');
     },
     play:()=>{
         console.log('我没在打游戏');
     }
 }

对象解构和展开运算符

let person = {
name: "小华",
gender: "男",
address: "武汉",
};

// 解构
let {name,gender} = person

//组装对象
let NewPerson = {...person,age:18}
///展开
let {name,...rest}=person

7.接口

关键词 interface 只能指定对象类型 可以继承 在type之前出来, 在无需继承的场景下可以使用type代替,

接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I 开头

interface Istu{
   name:string
   sex:string
   score:number
   height:number
   study():void
   play:()=>void
}
const stu3:Istu={
   name:'阿斯顿',
   sex:'男',
   score:99,
   height:175,
   study:function(){
       console.log('我在学习');
   },
   play:()=>{
       console.log('我在打游戏');
   }

使用接口的继承

//格式
interface 接口2 extends 接口1 {
  属性1: 类型1// 接口2中特有的类型
  ... 
}


//例子
interface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
z: number
}

继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)

8.元祖

元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值

作用-->约定了元素的个数---约定了特定索引对应的数据类型

//简单
const arraylist :[number,string]=[100,'abc']

//对象
type Position1 = { dimension: number, longitude: number }
let p1:Position  = {dimension:116.2317, longitude: 39.5427}

//数组
type Position2 = Array<number>
let p2 Position2 = [116.2317, 39.5427]

简单模拟定义一个useState (只有number类型)

const useState =(list:number):[number,(newlist:number)=>void]=>{
 const fn=(newlist:number)=>{
     list = newlist
 }
 return [list,fn]
}


const [list,uselist] = useState(100)
console.log(list,'list');
uselist(100)

9.字面量类型

let s1 ='hellow Ts'
const s2 = 'hellow Ts'

//联合使用
type Gender = 'girl' | 'boy'
let g1 :Gender = 'boy'
let g2 :Gender = 'girl'

解释:

  1. s1是一个变量 值可以说任意字符串 类型为string
  2. s2是一个常量 值不能变化 类型为 'hellow TS'
  3. 'girl'和'boy'就是一个字面量类型 某个特定的字符串可以作为TS的类型
  4. 字面量一般和联合类型一起使用,表示只能取某些个特定的值。

字面量.png

10.enum(枚举类型)

定义:一个被命名的整型常数的集合,用于声明一组命名的常数

格式:

enum 枚举名{
   标识符①[=整型常数],
   标识符②[=整型常数],
   ...
   标识符N[=整型常数],
}枚举变量;

1.数字枚举

注:当声明一个枚举类型并且没明确赋值时,TS默认为number类型 并且从下标0开始依次累加:

///没有明确赋值时
enum Direction {
    Up,   // 值默认为 0
    Down, // 值默认为 1
    Left, // 值默认为 2
    Right // 值默认为 3
}

console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true

//给第一个赋值后 后面的值也会根据前一个值进行累加1
enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13

2.字符串枚举

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

console.log(Direction['Right'], Direction.Up); // Right Up

///当其中一个变量为字符串时 其他变量也要赋值字符串 否则会报错
enum Direction {
 Up = 'UP',
 Down, // error TS1061: Enum member must have initializer
 Left, // error TS1061: Enum member must have initializer
 Right // error TS1061: Enum member must have initializer
}

3.组合使用(number和string)

enum NandStrEnum{
abc='abc',
score=99,
}

4.合并操作

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

enum Direction {
    Center = 1
}


///JS代码:
var Direction;
(function (Direction) {
    Direction["Up"] = "Up";
    Direction["Down"] = "Down";
    Direction["Left"] = "Left";
    Direction["Right"] = "Right";
})(Direction || (Direction = {}));

(function (Direction) {
    Direction[Direction["Center"] = 1] = "Center";
})(Direction || (Direction = {}));

得出结论:Direction对象属性会叠加

简单的枚举类型应用场景

enum Gender {
      girl,
      boy
}
type User = {
     name: string,
     gender: Gender
}

const u1: User = {
     name: '小花',
     gender: Gender.girl // 写代码的时候,可以利用代码提示
}

console.log(u1)

11.any类型

注:顾名思义,any: 任意的。当类型设置为 any 时,就取消了类型的限制。

let obj: any = { x: 0 }

obj.bar = 100
obj()
const n: number = obj

并不推荐使用any 因为会让TypeScript 变为 AnyScript

12.类型断言

作用-->手动指定值得类型

使用场景: 有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。

as语法

例子1:

const box = document.getElementById('img') as HTMLImageElement

console.log(box.src);//类型'HTMLElement'上不存在属性src 给box上加 as  HTMLImageElement

// document.createElement('img') -----HTMLImageElement

例子2:

知道后端的结果会是一个类型,但这个结果是用ajax拿到的,不好直接给初始值,又希望得到输出提示的效果

type obj12={
    name:string,
    age:number
}
let u1 = {} as obj12
console.log(u1.name)

尖括号语法

let someValue = "abcdefg";
let strLength: number = (<string>someValue).length;

非空断言

操作符: !

作用--> 在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型

(1)忽略undefined和null 类型
function myFn(mystring: string | undefined | null) {
   const onlyString: string = mystring; // Error
   const ignore: string = mystring!; // Ok
 }
(2)调用函数时忽略 undefined 类型
  type NumGenerator = () => number;

  function myFn(numGenerator: NumGenerator | undefined) {
    const num1 = numGenerator(); // Error
    const num2 = numGenerator!(); //OK
  }

13.typeof

作用-->可以用来获取变量或属性的类型

使用场景: 根据已有变量的值,反向推断出获取该值的类型,来简化类型书写

格式:

type 类型 = typeof 常量

例子:

// typeof
let res ={name:'asd',age:12, skills: ['js', 'css'] }

type p = typeof res //读取 res中的类型

function ff(obj:p) {
   console.log(obj.skills,'typeof'); //引用了该类型时 会有提示
}

//typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)

14.keyof

作用-->获取某个对象/类型的属性名来构成新类型

格式:

type 类型 = keyof 类型
type 类型 = keyof 对象常量

例子:

type key1 = {x:string,y:number}
type key2 = keyof key1
// key2 ---- x | y


let p:key2='x'

type  key3=keyof {a:1,b:2}
// key3 ----"a" | "b" ----获取该对象的a和b (相当于键)

let p1:key3='b'

15.unknown

和any的区别:unknown是更加安全的any类型 例子:

// 没有类型检查就没有意义了,跟写JS一样
// 不安全
let value:any
value = true
value = 1
value.length

解决不能调用unknown的方法:

let value:unknown// 定义为
value = 'abc'// 类型断言
console.log( (value as string).length )

// 类型收窄
if (typeof value === 'string') {
  value.length
}

unknownany 的主要区别是 unknown 类型会更加严格 在对 unknown 类型的值执行大多数操作之前 我们必须进行某种形式的检查 而在对 any 类型的值执行操作之前 我们不必进行任何检查 所有类型都可以被归为 unknownunknown类型只能被赋值给 any 类型和 unknown 类型本身 而 any 什么都能分配和被分配

泛型(重点)

设计的目的: 设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

格式:

function 函数名<类型变量1,类型变量2,...>(参数1:类型1,参数2:类型2,...): 返回值类型 { 
}

在函数名称的后面写 <>(尖括号),尖括号中添加类型变量

类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)

----简单的例子
function fn1<T>(value:T):T{return value}//T 为变量 接收变化的类型

// 标准写
const n1=fn1<number>(100)
//简写
const n2=fn1(100) //类型推断出是number类型
----简写一个useState
function useState<T>(value:T) { //简单写
    const setValue = (newValue:T):void => {
    }
    return [value, setValue]
}

let [strs, setStr] = useState('saa') //简单写时 strs会出现不确定的两种类型  string | ((newValue: string) => void)

const [num, setNum] = useState(123) //简单写时 num会出现不确定的两种类型  number | ((newValue: number) => void)

useState--优化
// --多种类型(泛型)----明确返回值类型(元祖)
function useState<S>(value:S):[S,(value:S)=>void] {
    type fn=(newValue:S)=>void
    const setValue:fn = (newValue) => {
    }
    return [value, setValue]
}

const [s12, setStr1] = useState('123')

const [num2, setNum1] = useState(123)

泛型接口

interface IdentityFn<T> {
    (age: T): T;
  }

泛型约束

背景: 默认情况下,泛型函数的类型变量 T 可以代表多个类型,这导致在泛型函数内部无法访问任何属性

简单约束

function fn222<T>(value:T[]):T[]{
    console.log(value.length);
 return value
}

接口+约束

interface Ilength {length:number}
//继承接口里规定好的属性number类型 就能获取到length  长度
function fn123<T extends Ilength>(value :T):T{
    console.log(value.length);
    return value
}

应用

//应用--创建函数获取对象中属性的值
function ob<T, kkk extends keyof T>(obj:T,key:kkk){
 return obj[key]
}
let jj={name:'qqq',age:12}
ob(jj,'age')

泛型工具

Partial

作用--> 用来基于某个Type来创建一个新类型,新类型中所有的属性是可选的。--统一设置可选

type OldType ={name:string,age:number}
type NewType = Partial<OldType>

// 构造出来的新类型 NewType 结构和 OldType 相同,但所有属性都变为可选的

Readonly

作用--> 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)--不可修改。

type Props = {id:string,name:string,age:number}
type ReadonlyProps = Readonly<Props>

let obj1111 :ReadonlyProps={
    id:'1',
    name:'小龙',
    age:12
}
// obj1111.age='12' // 报错--只读属性不能修改

Pick

作用--> 从已有的类型中挑选一组属性,来构造新类型。

<老类型,挑选的属性>

type props ={id:string,name:string,age:number}
type Pickprops = Pick<props,'name'|'age'>
//  Pickprops 现在只有 props中的 name 和 age 属性

in

作用-->用来遍历枚举类型

type key = 'q' | 'w' | 'e'

type obj ={
[p in key]
}

infer

作用-->声明一个类型变量并且对它进行使用。

type Rettype<S> = S extends ( ...args: any[] ) => infer R ? R : any;

泛型.png

初体验TS可能会报的错误

console.log报错问题

Cannot find name 'console'. Do you need to change your target library? Try changing thelib compiler option to include 'dom'.

原因:没有创建ts项目,没有写配置文件

解决:用tsc --init命名,在根目录下生成配置文件 tsconfig.json

同名的变量冲突问题

原因:目前写的代码不是模块化的环境,定义的变量都是全局的。

解决:

  1. 方式1:写代码时,用{ }整体给包起来
  2. 方式2:export {}