ts基础教程(一)

ts基础教程

安装和环境搭建

1. 全局安装typescript

npm install -g typescript

2. 项目初始化

一般而言,ts都是编译成js后再执行js,如果想一步到位,可以安装ts-node

  • 新建目录(项目名可自定义):mkdir ts-dev
  • 进入项目文件夹ts-dev: cd ts-dev
  • 生成package.json文件: npm init -y
  • 生成tsconfig.json文件: tsc --init
  • 本地安装ts和ts-node:npm i typescript ts-node

3. package.json中配置编译和运行脚本

"scripts": {
    "build": "tsc -w",  // 编译ts为js,参数-w表示实时监听文件变化
    "start": "ts-node ./src/index.ts" //一步到位的运行ts代码
  }

4. tsconfig.json常用配置

{
  "compilerOptions": {
    "target": "es5",                            // 指定 ECMAScript 目标版本: 'ES5'
    "module": "commonjs",                       // 指定使用模块: commonjs/amd/system/umd/es2015
    "moduleResolution": "node",                 // 选择模块解析策略
    "experimentalDecorators": true,             // 启用实验性的ES装饰器
    "allowSyntheticDefaultImports": true,       // 允许从没有设置默认导出的模块中默认导入。
    "sourceMap": true,                          // 编译后同时生成对应的 map路径映射文件
    "strict": true,                             // 启用所有严格类型检查选项
    "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错
    "alwaysStrict": true,                       // 以严格模式检查模块,并在每个文件里加入 'use strict'
    "declaration": true,                        // 生成相应的.d.ts文件
    "removeComments": true,                     // 删除编译后的所有的注释
    "noImplicitReturns": true,                  // 不是函数的所有返回路径都有返回值时报错
    "importHelpers": true,                      // 从 tslib 导入辅助工具函数
    "lib": ["es6", "dom"],                      // 指定编译中的库文件,部分es6新特性需要依赖es6lib
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",                           //编译后出口
    "rootDir": "./src",                           //根目录入口
    "noEmitOnError":true                          //编译不通过不生成编译后文件,默认会生成
  },
  "include": [                                  // **/ 表示递归匹配任意子目录
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}

5. hello world

  • ts-dev下新建src文件夹: mkdir src
  • src下新建index.ts: cd src && touch index.ts
  • 在index.ts 中写入如下代码
//ts 中,使用冒号: 指定变量的类型,冒号 :的前后有没有空格都可以。
// 注意:函数有参数约束和返回值约束两种约束
function sayHello(person: string):string {
    return 'Hello, ' + person;
}

const user = "冷月心"
console.log(sayHello(user));
  • 编译 :npm run build
  • 项目文件夹下会出现一个dist文件夹
  • 编译后的文件dist/index.js

dist/index.js

"use strict";
function sayHello(person) {
    return 'Hello, ' + person;
}
var user = "冷月心";
console.log(sayHello(user));
//# sourceMappingURL=index.js.map

6. noImplicitAny属性配置测试

tsconfig.json中我们配置noImplicitAny为true,这意味着在表达式和声明上有隐含的 any类型时报错。这其实是一个严谨的编码习惯,anyScript会削弱静态类型检查的价值。

  • 亲测:函数返回值的any检测不到,书写时候要注意

  • 修改src/index.ts如下

function sayHello(person) {
    return 'Hello, ' + person;
}

const user = "冷月心"
console.log(sayHello(user));
  • 编译失败:Parameter ‘person’ implicitly has an ‘any’ type.

7. noEmitOnError属性配置测试

默认情况下,即使编译不通过也会生成编译后文件。设置noEmitOnError属性为true,上述操作6隐含any类型导致的编译不通过,就不会生成编译后文件

基础类型

js基础类型分为两种:原始数据类型和对象类型,其中原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 es6系列中的 symbol,bigint。

1. 布尔/数值/字符串

const bol: boolean = true;
const num: number = 1024;
const userName: string = "冷月心";
//字符串模板依旧能用
const str:string=`${userName}`

2. null和undefined

//这两个类型只有 自己
const onlyNull: null = null;
const onlyUndefined: undefined = undefined;

3. function

声明的函数在调用的时候,参数个数要和声明时候的参数个数保持一致


//没有返回值的函数可以用void声明
const fn1 = (param1:string,param2:number): void => {
  console.log("我是没有返回值的箭头函数");
};

function f2(param1:string,param2:number):void{
  console.log("我是没有返回值的普通函数");
}

//有返回值的箭头函数声明是这样的
const fn3 = (): string => {
  return "冷月心=>"
};

//有返回值的普通函数声明是这样的

function f4():string{
   return "冷月心fn"
}


//函数表达式的双向限定
//上述fn1其实只对=右侧做了限制,对左侧并没有
//完善一点,可以这样 => 用来表示函数的定义,左输入类型,需要用括号括起来,右输出类型

const fn1:(param1:string,param2:number)=>void = (param1:string,param2:number): void => {
  console.log("我是没有返回值的箭头函数");
};

// 函数的可选参数
// 注意可选参数要在确定参数后
function f5(name:string,age?:number):string{
   return "冷月心fn"
}

//函数参数默认值
function f6(name:string,age:number=18):string{
   return `${name}--${age}`
}
//此时可选参数不必一定在确定参数后,但是调用有问题
function f7(name:string,desc?:string,age:number=18):string{
   return `${name}--${age}--${desc}`
}

//剩余参数
function f8(...args:number[]):number[]{
  return args
}
console.log(f8(1,2,3,4,5))//[1,2,3,4,5]

4. void声明变量

void也可以用来声明变量,但只能作用在undefined身上,null也不行。只有tsconfig.json中strictNullChecks属性设置为false时,null才能赋值给void

const u:void=undefined;//这是个鸡肋用法,基本不会这么用
const n:void=null;//这样会报错
const age:void=18;//这样也会报错

5. symbol

symbol使用依赖es6编译辅助库 ,tsconfig.json lib[“es6”]

const sym1:symbol = Symbol();
const sym2:symbol = Symbol();
console.log(sym1===sym2)//false

6. bigint

bigint可以安全地存储和操作大整数, 目前兼容性不是很好


// 超出最大整数的计算会超精度,得不到期望值 

const max = Number.MAX_SAFE_INTEGER;
const max1 = max + 1
const max2 = max + 2
console.log(max1===max2)// true ,实际应该不相等


// 需要将数值转成BigInt计算,不会超精度,以下为js代码
const big_max = BigInt(Number.MAX_SAFE_INTEGER);
//这里n是bigint的标志,且bigint和number是完全不同的两个类型
//ts中使用bigint,不支持1n,2n,但是可以BigInt(1),BigInt(2)代替
const big_max1 = big_max + 1n
const big_max2 = big_max + 2n
console.log(big_max1 === big_max2) // false 这才是预期结果

任意值

任意值(any/unknown)用来表示允许赋值为任意类型

  • 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
  • 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型(隐式,noImplicitAny属性可以校验到)
  • 了解后分分钟忘了即可,这不是什么好习惯

1. any

//非any类型不可以跨类型赋值
let str1:string="冷月心";
str1=123//报错

//any类型可以跨类型赋值,这就像js本身
//注意,这种属于显式声明any类型,tsconfig.json中noImplicitAny属性校验的是隐式
let str2:any="冷月心"
str2=123//不报错

2. unknown

//any类型下,可以在任意值上访问任何属性,调用任何方法,但是unknown不行

let str3:any="冷月心"
str3.say();//不报错
str3.age //不报错

//unknown是更安全的any
let str4:unknown="冷月心"
str4.say();//报错
str4.age //报错

数组

如果你学过java,那你应该更容易理解ts中数组的两种定义方式,选个喜欢的就好

//像java中的集合+泛型,List<String> arr=new ArrayList();
const arr1: Array<string> = ["冷","月","心"] 
//类型后+[],java中也是如此:String [] arr = {"冷","月","心"};
const arr2: string[] = ["冷","月","心"]

//数组的项中不允许出现其他的类型
const arr3: string[] = ["冷","月","心",1024]//报错 1024非字符串

元组

ts中的元组和js的数组很像,可以存储不同类型的值

1. 基本使用

元组使用过程中要保证元组类型定义的顺序和填充类型的顺序一致,数量一致,可以按索引访问


const tuple1: [string, number]=["冷月心",24]//这样是ok的

//按索引访问,其实编译后就是js数组 ["冷月心", 24]
console.log(tuple1[0])//冷月心

//这样会报错
const tuple2: [string, number]=[24,"冷月心"]//顺序不一致
const tuple3: [string, number]=["冷月心"]//缺少
const tuple4: [string, number]=["冷月心",24,25]//多余

2. 元组越界

ts允许向元组中使用数组的push方法插入新元素(但不允许访问)

const tuple5: [number, number] = [1, 2];
tuple5.push(3); // 正常运行
console.log(tuple5); //  正常运行 [1,2,3]
console.log(tuple5[2]); //访问新插入的元素会报错

枚举

枚举类型可以由枚举值得到它的名字,这种感觉像对象的键值对。

  • 枚举类型也确实属于对象类型
  • ts只支持基于数字和字符串的枚举
  • 对于基于数字的枚举类型支持键值对的反向映射 key<=>value
  • 基于字符串的不可以反向映射?当然不可以,就是纯js对象
//这样不行
enum Flag{
  open=true,
  close=false
}

1.数字枚举-默认增长

当我们不在乎成员的值的时候,这种自增长的行为是很有用的,但是要注意每个枚举成员的值都是不同的。

enum Language{
  java,
  node,
  php,
  python
}

//可以按值访问
console.log(Language[0])//java
//也可以按键访问
console.log(Language["java"])//0

//打印结构如下
console.log(Language);

{
  '0': 'java',
  '1': 'node',
  '2': 'php',
  '3': 'python',
  java: 0,
  node: 1,
  php: 2,
  python: 3
}

//编译后代码如下 IIFE传参的形式
"use strict";
var Language;
(function (Language) {
    Language[Language["java"] = 0] = "java";
    Language[Language["node"] = 1] = "node";
    Language[Language["php"] = 2] = "php";
    Language[Language["python"] = 3] = "python";
})(Language || (Language = {}));
console.log(Language);
//# sourceMappingURL=index.js.map

//可以看到内层赋值生成的是
{
  java: 0,
  node: 1,
  php: 2,
  python: 3
}
//外层赋值生成的是
{
  '0': 'java',
  '1': 'node',
  '2': 'php',
  '3': 'python',
}

2.数字枚举-自定义增长

当我们需要成员的值按照我们预期设定的的时候,就需要手动设置枚举的增长值。当然,未设置的值会根据上下文增长.

enum Language{
  java=5,
  node,
  php,
  python
}

//打印一下
console.log(Language)

{
  '5': 'java',
  '6': 'node',
  '7': 'php',
  '8': 'python',
  java: 5,
  node: 6,
  php: 7,
  python: 8
}

//当然你也可以全部手动指定每个值,不连续也可以

enum Language{
  java=100,
  node=101,
  php=103,
  python=104
}

3. 字符串枚举

其实就是把上述数字枚举的值换成字符串,但结构有些不同,再次证明,枚举属于对象类型

enum Language{
  java="J",
  node="N",
  php="P",
  python="PY"
}

//打印一下
console.log(Language)

{ 
 java: 'J',
 node: 'N', 
 php: 'P', 
 python: 'PY' 
}


//看看编译后的js

"use strict";
var Language;
(function (Language) {
    Language["java"] = "J";
    Language["node"] = "N";
    Language["php"] = "P";
    Language["python"] = "PY";
})(Language || (Language = {}));
console.log(Language);
//# sourceMappingURL=index.js.map

4. 异构枚举

这不是新类型,属于衍生类型,包含数字和字符串组合的枚举

enum SwitchEnum {
    open = 1,
    close= "0",
}

//编译后
"use strict";
var SwitchEnum;
(function (SwitchEnum) {
    SwitchEnum[SwitchEnum["open"] = 1] = "open";
    SwitchEnum["close"] = "0";
})(SwitchEnum || (SwitchEnum = {}));
//# sourceMappingURL=index.js.map

对象

ts在对象的使用上有着一些限制,必须是特定类型的实例,常常配合interface使用。

  • interface定义的属性分隔用分号/逗号/空着 都可以

1. 基本使用


//创建一个空对象
const obj:object={};

//也许你想这样给对象增加属性
//但是不行,会报错,因为最初是一个空对象,自然没有这个name属性
obj.name="tom"

//这就需要好用的interface出场了
//如果你用过java,那应该清楚接口是一套待实现的规范
//和元组规范类似,实例属性与声明类型的属性数量和名称严格一致,不可多不可少
interface User{
  name:string
}

const u1:User={
  name:"冷月心"
}
//报错,多属性
const u2:User={
  name:"冷月心",
  age:24
}
//报错,少属性
const u3:User={}

//若属性值为函数,也需要在接口中声明
interface Person{
  name:string,
  say: (something: string) => string
}

const p:Person={
  name:"jack",
  say: (something: string) => something
}

console.log(p.say("hello"))//hello

2. 可选属性

有些时候我们希望某些属性是可选的,使用?修饰

interface Stu{
  name:string,
  desc?:string
}

//此时缺少desc属性也是可以的,因为它是可选属性
const s:Stu={
  name:"tom"
}

3. 任意属性

有些时候我们允许任意属性的添加,但是其使用和表现显得有些让人费解

interface Stu {
    name: string;
    sno?: number;
    [prop: string]: any;
}

//这样写ok
const s1: Stu = {
    name: 'Tom',
    sex: '男'
};
//这样写也ok,可选属性没什么影响
const s2: Stu = {
    name: 'Jack',
    sex: '男',
    sno:10010
};


3.1 费解点一

const s: Stu = {
    name: 'Join',
    sex: '男',
    sno:10010,
    className:"A班",
    desc:"热爱编程"
};

3.2 费解点二

prop: string: any; 也许你觉得任意属性只能是string类型,其实是any

//这些都没问题,正常运行
const s: Stu = {
    name: 'Join',
    sex: '男',
    sno:10010,
    className:"A班",
    desc:"热爱编程",
    hobit:["吃饭","睡觉","打豆豆"],
    soulmate:{},
    flag:true
};

3.3 费解点三

prop: string: any; 也许你觉得[prop:string]是固定写法,其实prop只是个自定义变量,你可以简写成p,甚至a,b,c,d,但是只有prop: string,[prop: number] 这两种写法

  • 你以为[prop: number]意味着属性值可以是number或any??
interface Stu {
    name: string;
    [prop: number]: any;
}

//以下皆报错
const s1: Stu = {
    name: 'Jack',
    sex: '男',
};

const s2: Stu = {
    name: 'Jack',
    age: 18,
};

const s3: Stu = {
    name: 'Jack',
    hobit:["吃饭","睡觉","打豆豆"],
};
const s4: Stu = {
    name: 'Jack',
    soulmate:{},
};
const s5: Stu = {
    name: 'Jack',
    flag:true
};

//好的,关键点来了
//[prop:number]:any,意味着属性名必须是数字,值可以任意,以下都ok

const s6: Stu ={
  name:"jack",
  0:true,
  1:[],
  2:{},
  3:"冷月心",
  4:Symbol()
}
   

4. 对象类型的子类型

//看看对象类型的子类型
enum Like {
   coding
}

const arr:number[]=[1,2,3];
const tuple:[string,number]=["冷月心",1024]
const fn=():void=>{} 

//之前我们说过,ts不允许跨类型赋值
//以下代码都会正常运行,这就意味着它们都是对象子类型
obj=Like;
obj=arr;
obj=tuple;
obj=fn;

5. 只读属性

有时我们希望对象某些属性自赋值后不被更改,使用readonly修饰即可

interface Stu {
    readonly sno:number
}

//学生学号自赋值后不允许更改
const s:Stu={
  sno:10010
}

s.sno=10011 //报错 Cannot assign to 'sno' because it is a read-only property.

参考链接

  • 掘金ts小册,https://juejin.im/book/5da08714518825520e6bb810
  • ts中文手册,https://zhongsp.gitbooks.io/typescript-handbook
  • ts入门教程,https://ts.xcatliu.com
原创文章 451 获赞 859 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_42813491/article/details/104681064