前言
能用 Javascript 写的东西,终将会用 TypeScript 书写
本文思维导图
TypeScript的意义
- 避免一些类型或者一些不是我们预期希望的代码结果错误
JavaScript
中 很多错误是运行时才会抛出来的,而 TypeScript
可以做到出现错误时在编辑器里就能获知,提高了开发效率
以下
JavaScript
和Typescript
都由js
和ts
代替
TypeScript优缺点
优点
js
开发中,进行前后端联调时,需要去查看接口文档上的字段类型,而ts
会自动识别(类型推断),节省时间-
缺点
学习成本
- 类型概念、interface接口、class类、enum枚举、generic泛型
- 与一些其他的库的融洽性
js & ts 对比
运行流程
js 运行流程
剩下三步就是 js 的运行流程
- 将 js 代码转为 js-AST
- 将 AST 转换为字节码
- 运行时计算字节码
其他区别
| 类型系统特性 | JavaScript | TypeScript | | —- | —- | —- | | 类型是如何绑定? | 动态 | 静态 | | 是否存在类型隐式转换? | 是 | 否 | | 何时检查类型? | 运行时 | 编译时 | | 何时报告错误 | 运行时 | 编译时 |
类型绑定
- js 是动态绑定语言,只有运行程序时才能知道类型,在程序运行之前 js 完全不知道。
ts 在编译时就已经知道什么类型,如果没有定义类型,也会自动推导类型
类型转换
js 会有隐式转换
ts 不会
js 运行时
-
何时报告错误
js 执行后
- ts 写代码时
主要使用模式
显式注解类型
也就是声明变量时带上类型注解let name: string = "zzz";
let age: number = 18;
let hobby: string[] = ["code", "gogogo"]
推导类型
它会自动推导是什么类型,当你在 vscode 中将鼠标放上去就能看见let name = "zzz";//string
let age= 18;//number
let hobby= ["code", "gogogo"]//string数组
开玩
安装
npm i -g typescript
全局安装执行
tsc index.ts
执行之后,就会编译生成一个index.js
,也就是上文提到的ts转换为js的那一步。ts-node
每次都自己手动编译的话——麻烦,这里介绍一个插件ts-node
,直接运行.ts 文件,并且不会编译出来一个新的 js 文件使得目录看起来非常冗余实际上,用像 cra 等脚手架的话,这些完全不需要自己操作,具体使用只需要知道 ts 的语法就行了
安装npm i ts-node
执行ts-node index.ts
基础知识
基础静态类型
前面几个比较简单就只举一个例子
boolean
const status: boolean = false; // 显示注解一个boolean g类型
const status1 = true; // 不显示注解,ts会自动推导出来类型
number
string
null
undefined
- void (表示无效的意思
一般只用在函数上,表示该函数没有返回值
function fn(): void {} // 正确
function testFn(): void {
return 1; // 报错,不接受返回值存在
}
function fn1(): void { return undefined} // 显示返回undefined类型,也是可以的
function fn2(): void { return null} // 显示返回null类型也可以,因为 null == undefined
- never (表示永远不会有值或永远执行不完的类型
甚至 null、undefined也不能返回
const test: never = null; // 错误
const test1: never = undefined // 错误
function a(): never { // 正确,因为死循环了,一直执行不完
while(true) {}
}
function b(): never { // 正确,因为递归,永远没有出口
b()
}
function c(): never { // 正确 代码报错了,执行不下去
throw new Error()
}
- any (表示任意
全都用这个的话——就是和 js 定义类型一样。
- unknown (表示未知,和any很像
区别在于:
- 未知,代表你可以容纳任何类型——因为你也不知道你能装什么,索性都装了。但是,别人就不一样了,别人知道自己要装什么,你是未知类型,不是我要装的—— 别人赋值给你,行!你赋值给别人,不行!
- 而any,就是橡皮泥,啥形状都行。
对象静态类型
object
也就是{}
const list: object = {} // 空对象
const list1: object = null; // null对象
const list: object = [] // 数组对象
const list: {} = {}
list.name = 1 // 报错
list.toString()
对象类型常与后面说到的接口一起使用
数组 && [ ]
const list: [] = []; // 定义一个数组类型
const list1: number[] = [1,2] // 定义一个数组,里面值必须是number
const list2: object[] = [null, {}, []] // 定义一个数组里面必须是对象类型的
const list3: Array<number> = [1,2,3] // 泛型定义数组必须是number类型,泛型后面说
类
class ClassPerson = {}
const person: ClassPerson = new Person();
person.xxx = 123; // 这行代码报错,因为当前类中不存在该xxx属性
函数
const fn: () => string = () => "zzz" // 定义一个变量必须是函数类型的,返回值必须是string类型
函数类型注解
函数是不会自动类型推导的。——但你可以显式地定义注解类型。
function testFnQ(a:number, b:number) :string {
return a + b
}
testFnQ(1,2)
参数类型设为
number
,返回类型为string
。
ps. 表示无返回值的void
已经在上面说了。
参数是对象的情况:function testFnQ(obj : {num: number}) {
return obj.num
}
testFnQ({num: 18})
元组Tuple
表示一个已知数组的数量和类型的数组,定义数组中每一个值的类型(不常用
const arr: [string, number] = ["zzz", 1]
枚举Enum
使用场景
控制有些取值是在一些特定的范围的常量,比如
一周七天
- 方向东南西北或上下左右。
简单使用
enum e {
a,
b = "B",
c = "C",
d
}
// e["a"] 0
// e["b"] B
// e["d"] 报错
乍一看,有点像json
。但是他对于默认值的设置是没有那么灵活的——如上,如果c设置了默认值,d就不知道”C”递增后是啥了,但如果c=10
,那么d就会输出11。
还可反查:
enum e {
a,
b = 9,
c = 10,
d
}
// e[0] a
// e[9] b
// e[10] c
// e[11] d
深入了解
众所周知,原本的js
中不像python
是没有这枚举类型的,然后我就有些好奇,它编译后的样子。
ts
enum Direction {
Up
Down,
Left,
Right,
}
js
var Direction;
(function(Direction){
Direction[Direction["Up"]= 0] = "Up";
Direction[Direction["Down"]= 1] ="Down;
Direction[Direction["Left"]= 2] ="Left";
Direction[Direction["Right"] =3] = "Right";
})(Direction ||(Direction = {}))
有点妙哈…通过这样达到双向赋值的效果:Direction["Up"]= 0
,然后赋值运算符返回的是被赋予的值,也就是说Direction[Direction["Up"]= 0] = "Up";
也就等于Direction[0] = "Up";
接口Interface
关于接口,我觉得了解鸭子类型也许对你有所帮助。
六个字概述接口作用就是:方便复用代码。
没用接口前:
const testObj: { name: string, age: number } = { name: "okk", age: 18 }
const testObj1: { name: string, age: number } = { name: "joo", age: 18 }
可以发现很多重复的代码——用上接口即可改善:
interface Types {
name: string,
age: number
}
const testObj:Types = { name: "okk", age: 18 }
const testObj1: Types= { name: "joo", age: 18 }
修饰符readonly
? 可选修饰符
interface Types {
name: string,
age: number,
height?:number
}
接口定义、使用后,name、age属性是必填的,而height则不是。
继承extends
接口也是可以继承的——和 Class 一样
interface ParentType {
name: string,
age: number,
height?:number
}
interface Child extends ParentType {
weight:number
}
扩展propName
interface ParentType {
name: string,
age: number,
height?:number,
[propName:string]:any //propName字段必须是 string类型 or number类型。 值是any类型
}
如果接口中没有第四个属性,那么定义对象时,是不能私自增加不存在的属性的。
声明类型别名 Type
别名类型只能定义是:基础静态类型、对象静态类型、元组、联合类型。不能定义接口。
type Type1 = string;
type Type12 = string | number
const name: type1 = "zzz"
const age: type12 = 18
const c: type12 = "zzz"
关于 Type 和 interface 的区别
名字
首先从这两个的中文名称入手:
interface(接口)
是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。type (类型别名)
,顾名思义,类型别名只是给类型起一个新名字。它并不是一个类型,只是一个别名而已使用与属性
其次是他们的使用与属性:
定义出来的Type 类型不允许重名。
- 接口是可以重名的,效果就是合并——当然后续接口定义里面的同名属性的类型是要与之前保持一致的,不然编译器会报错
- type支持表达式;interface不支持 ```typescript const count: number = 123 type testType = typeof count
const count: number = 123
interface testType { [name: typeof count]: any // 报错 }
- type 支持类型映射,interface不支持
```typescript
type keys = "name" | "age"
type KeysObj = {
[propName in keys]: string
}
const PersonObj: KeysObj = { // 正常运行
name: "zzz",
age: "18"
}
interface testType {
[propName in keys]: string // 报错
}
还有显而易见的一点:type定义时使用 = ,interface没有😏
其实我总感觉大部分时候使用都是看心情😛
联合类型 |
就是或——满足其中一个即可。
函数参数尽量不直接使用联合类型(至少要进行类型保护),函数参数类型本身就不能自动推导,如有调用访问参数类型上的方法,系统编译时并不知道有没有该方法,或者该方法长啥样。
类型保护
typeof
判断一下,参数是不是这类型,是就往下走。
function fun(params: string | number) {
if (typeof params == "string") {
console.log(params.split)
}
if (typeof params == "number") {
console.log(params.toFixed)
}
}
in
看看参数里面有没有想操作的东西,有就操作。
interface a {
name: string
}
interface b {
age: string
}
function fun(params: a | b) {
if ("name" in params) {
console.log(params.name)
}
if ("age" in params) {
console.log(params.age)
}
}
as断言
可以用来手动指定一个值的类型,后接一个接口
当 ts 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
主要是用于“欺骗”编辑器——“这个animal类型就是Fish啦,往下走就好了”。但是实际运行时会不会出错呢…就看你自己写的对不对了,骗进去了发现真的没有方法就出问题了。
感叹号+点 !.断言
!. 的意思是断言,告诉 ts 该对象里一定有某个值
const inputRef = useRef<HTMLEInputlement>(null);
// 定义了输入框,初始化是 null,但是你在调用他的时候相取输入框的 value,这时候 dom 实例一定是有值的,所以用断言
const value: string = inputRef.current!.value;
// 这样就不会报错了
交叉类型 &
就是与——类型必须存在。每个接口中的所有属性都要传入,如果有同名属性,类型却不一样,类型则都会变成never
⭐泛型
有时想要函数传入的所有参数类型相同:可以都是number,也可以都是string——只要都是就行。如果一个个写,非常麻烦。就可以用上泛型来解决这问题。
function fun<T>(a: T, b: T) {
console.log(a, b)
}
fun<number>(1, "zzz") // 这时报错,因为在调用的使用类型是number,只能都传入number类型
fun<boolean>(true, false)
fun<string>("ooo", "kkk")
fun<any>("jjj",111) //如果你又想让他们两不一样,用 any 就好了
有时我们想让他们不一样,但是又想约束一下范围。——使用 extends
function f<T1 extends number | string, T2 extends number | string>(a: T1, b: T2) {
console.log(a, b)
}
f<number, string>(18, "zzz")
f<string, number>("zzz", 18)
还有需要确保参数有某个属性的情况,就可以配合 接口 使用
interface IWithLength {
lenght:number,
}
function echoLength<T extends IWithLength>(arg:T):T{
console.log(arg.length)
return arg
}
模块
import a,{b} from './c'
export default xx
export const xxx
类 Class
public
private
只能在当前类里面访问——且只有类里面的方法才能访问以及对其进行操作。
protected
implements
就是 继承——可以继承父类(也可以用extends),也可以继承接口
interface Radio{
switchradio():void
}
//继承Radio接口之后,也就意味着告诉Car要实现这个switchRadio功能
class Car implements Radio{
switchRadio(){}
}
abstract
类中泛型
类也是可以使用泛型的
class Queue<T> {
private data = []
push(item:T){
return this.data.push(item)
}
pop():T{
return this.data.shift()
}
}
const queue = new Queue<number>()
命名空间
项目总有可能会有重名的变量——我们可以使用 namespace 定义一个命名空间来装各种变量,该暴露的就暴露,没暴露的——也就是内部变量,也就没有重名的风险。
namespace SomeNameSpaceName {
const x = {}//未暴露的
export interface xx {//暴露接口
name: string
}
}
引入
两种方法:
/// <reference path="./namespace0.ts" />
注意是三条斜杠,只能放在最顶端。
import { xx } from "./xxx.ts"
声明文件
简单使用:
jQery.d.ts
declare var jQuery:(selector: string) => any
一般声明为.d.ts后缀的文件,所有文件都可以访问到,如果不行则需要手动去配置一下tsconfig.json
的"include":["**/*"]
。
另外一般第三方库都会有写好的声明文件以供开发者使用,而不是像我上面那样操作。一般命名类似于@types/jquery
,使用npm
安装即可。
总结
看到这里了,话说我想问一下,现在还有地方是用 js 而不用 ts 的吗?
好了,阅读本文应该能让用过 TS 的稍微的复习一下简单的使用和基础语法,希望没用过的同学也能就此入门~ 下一篇我将带来 ts 中实用类型的相关知识~
🌊如果有所帮助,欢迎点赞关注,一起进步⛵