26-1
邂逅TypeScrpt
官网:typescriptlang.org
TypeScrpt是拥有类型的JavaScript超级,它可以编辑成普通、干净、完整的JavaScript代码
怎么理解上面的话呢?
- 我们可以将
TypeScript理解成加强版的JavaScript JavaScript拥有的特性,TypeScript全部都支持,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是至此的- 并且在语言层面上,不仅仅增加了类型约束,而且扩展了一些语法,比如枚举类型(
Enum)、元组类型(Tuple)等 TypeScript在实现新特性的同时,总是保持和ES标准同步甚至是领先- 并且
TypeScript最终会被编译成JavaScript代码,所以你不需要担心它的兼容性问题,在编译时也不需要借助于Babel这样的工具 所以我们可以把TypeScript理解为更加强大的JavaScript,不仅让JavaScrpt更加安全,而且给它带来了诸多好用的特性
TypeScript的编译环境
TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:
npm install typescript -g
安装后,可以使用下面命令来查看tsc(TypeScript compiler)的版本,它可以把TypeScript转换为JavaScipt
tsc --version
我们在.ts的文件中编写TypeScript代码,但是编写完后我们需要在终端中输入
tsc 对应的文件名
可以把TypeScript代码转换为JS代码,生成一个js文件
ts文件
let message:string='hello typescript'
function foo(payload:string){
console.log(payload);
}
foo('aaa')
export{}
输入命令生成的js文件
"use strict";
exports.__esModule = true;
var message = 'hello typescript';
function foo(payload) {
console.log(payload);
}
foo('aaa');
为什么需要在
ts中写export{}?因为在同一目录下,
ts代码都是在同一个作用域的,这样容易造成命名冲突,当我们使用export{}后,当前的ts文件就是独立的作用域,就不会造成命名冲突了
在node中运行TS
如果我们不想每次都使用命令来转换,而是直接运行,有什么好办法么?
安装ts-node可以解决这个问题
npm install ts-node -g
ts-node还依赖了tslib和@types/node这两个包,使用我们需要安装它们
npm install tslib @types/node -g
安装后,直接使用下面命令就可以直接运行ts代码了
ts-node xxx文件
webpack打包TS
26-1-45
使用webpack也是可以解决上面的问题的,那我们改怎么做呢?
-
先创建包管理文件
npm init -
安装
webpack,因为webpack需要使用webpack-cli,所以也需要安装npm install webpack webpack-cli -D -
创建
webpack.config.js进行配置安装
ts对应的loader// 虽然我们全局安装了ts 但是我们需要局部打包 所以我们还需要局部安装ts npm install ts-loader typescript -D使用下面命令创建
tsconfig.json文件,没有这个文件是打包不了ts文件的tsc --init安装
webpack-dev-sever,解决我们每次修改都需要重新打包的问题npm install webpack-dev-server -D将
index.html作为模板,需要安装html-webpack-pluginnpm install html-webpack-plugin -D配置
webpack.config.jsconst path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // 模式 mode: 'development', // 打包入口 entry: './src/main.ts', // 出口 output: { path: path.resolve(__dirname, './dist'), filename: 'bundle.js' }, // 配置查找的后缀名 resolve: { // 我们在package.json中配置了sever 但是dev-server是使用到了js 所以我们还需要配置js的后缀名 不然会出错 extensions: ['.ts', '.js', '.cjs', '.json'] }, // 配置loader与plugin module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ], }, // 配置plugin plugins: [ new HtmlWebpackPlugin({ // 使用的模板 template: "./index.html" }) ] }在
package.json中配置build与servebuild在安装webpack后就可以使用了serve在安装webapck-dev-server就可以使用了,如果还想要进行本地配置,可以在webpack.config.js中配置devServe"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "serve": "webpack serve" } -
配置完后就可以使用下面命令来运行了
npm run serve npm run build
TypeScript数据类型
细节
类型注解(type annotation):的后面应该为小写的string:TypeScript中的字符串类型,大写的String为JavaScript的字符串包装类的类型
const message: String = 'hello world'
const message: string = 'hello world'
类型推导:当我们没有给变量添加类型注解的时候,TS会对我们的代码进行类型推导自动定义类型
一般情况下,如果可以推导出类型注解时,可以不加类型注解
let message = 'hello world'
message = 123 // 报错
number类型
数字类型是我们开发走经常使用的类型,TS与JS语言,不区分整数类型(int)与浮点型(double),统一为number类型
在ES6中JS增加了二进制和八进制的表示方法,而TS也是支持二进制、八进制、十六进制的
let num: number = 123
// 十进制
let num1: number = 100
// 二进制
let num2: number = 0b111
// 八进制
let num3: number = 0o456
// 16进制
let num4: number = 0x123abcdef
console.log(num1, num2, num3, num4);
bool类型
let flag: boolean = true
flag = false
flag = 20 > 30
console.log(flag); // false
string类型
let message1: string = 'hello world'
// 默认情况下,如果可以推导出类型注解时,可以不加类型注解
const name = 'zs'
const age = 18
let message2 = `${name}: ${age}岁`
console.log(message2);
export { }
array类型
在数组中存放不同的类型是不好的习惯,所以在TS中可以使用类型注解使数组中的值只能为一种类型
与上面类型注解不一样的是,数组类型是大写的Array,而且定义数组中的值为那种类型需要使用到**泛型(<>)**或者使用:类型[]
// 第一种写法 不推荐 因为与jsx是有冲突的 jsx也是使用尖括号 <div></div>
const names1: Array<string> = []
// 第二种写法 推荐
const names2: string[] = []
names1.push('abc')
names2.push('cba')
names1.push(123) 报错
object类型
// TS会帮我们推导出来
const info1 = {
name: 'zs',
age: 18
}
// 如果我们使用类型注解 对象.xxx是拿不到数据的,会报错(编译通不过)
const info2: object = {
name: 'ls',
age: 20
}
console.log(info1.name);
console.log(info2.name); // 报错
null与undefined类型
null类型只有一个值null
undefined也只有只有一个值undefined
如果我们不给null类型注解,那么就是一个any类型,所以当我们是真的需要使用null的时候,加上null类型注解比较好
const n1: null = null
// n2为any类型
const n2 = null
let n3: undefined = undefined
symbol类型
const title1 = Symbol('title')
const title2 = Symbol('title')
const info = {
[title1]: '程序员',
[title2]: '老师',
}
export { }
any类型
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们就可以使用any类型(类似于Dart语言中的dynamic类型)
any类型有点像一种讨巧的TS手段
- 我们可以对
any类型的变量进行任何的操作,包括获取不存在的属性、方法 - 我们给一个
any类型的变量赋值任何的值,比如数字、字符串的值
let message: any = 'Hello World'
message = 123
message = true
message = {}
console.log(message);
export { }
如果我们对于某些情况的处理过于繁琐,不希望添加规定的类型注解,或者在引入第三方库时,缺失了类型注解,这个时候我们可以使用any
如果我们实在是不相使用
any,可以在tsconfig.js或者tslist里面配置
unknown类型
unknown类型只能赋值给any与unknown类型
any类型可以赋值给任意类型
function foo() {
return 'abc'
}
function bar() {
return 123
}
let flag = true
// 可以使用联合类型 string | number 但是我们有时候可能不知道函数返回的是什么类型
// 也可以使用 any 类型
// 但是推荐使用 unknown,防止我们拿到any类型到其他地方乱用
let result: unknown
if (flag) {
result = foo()
} else {
result = bar()
}
// 如果result为any类型 不会报错
let message: string = result // 报错
let num: number = result // 报错
console.log(result);
void类型
如果函数没有返回值的时候,TS会给我们推断出这个函数的类型是void类型
void类型是可以return null的
function sum(num1: number, num2: number) {
console.log(num1 + num2);
return null
}
sum(20, 30)
// 相当于
function sum(num1: number, num2: number): void {
console.log(num1 + num2);
}
never类型
never表示永远不会返回值的类型,比如:
- 如果一个函数走是一个死循环或者抛出一个异常,那么这个函数会返回东西么?
- 不会,那么写
void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型
function foo(): never {
// 死循环
while (true) { }
}
function bar(): never {
throw new Error()
}
应用场景:27-1
tuple类型
tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等
// 如果我们想让 zs 18 1.88 在数组中存储 那么我们只能使用any类型和元组类型
// 当然也可以使用对象
// 但是这样做是有缺点的,因为我们获得到的数据也是一个any类型
const info: any[] = ['zs', 18, 1.88]
const name = info[0] // any
const age = info[1]
console.log(name.length);
console.log(age.length); // undefined
// 那我们有什么办法使数组中的数据也是有自己的类型呢? 元组类型可以做到
// 每一个类型对应的需要赋一个值 不然报错
const info2: [string, number, number] = ['zs', 18, 1.88]
const name2 = info2[0] // string
const age2 = info2[1]
console.log(name2.length);
console.log(age2.length); // 报错
export { }
应用场景:27-1-20
函数类型
()=>void可以返回任何东西
// 1.函数参数类型
function foo() { }
// 函数类型定义的格式如下
type FooFnType = () => void
function bar(fn: FooFnType) {
fn()
}
bar(foo)
// 2.定义常量时,ts会推导出函数类型注解,当然我们也可以自己定义
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2
}
案例
function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
return fn(n1, n2)
}
const result1 = calc(20, 30, function (a1, a2) {
return a1 + a2
})
console.log(result1);
const result2 = calc(20, 30, function (a1, a2) {
return a1 * a2
})
console.log(result2);
函数参数与返回值类型
// 在开发中我们可以不写返回值类型,TS会自动推导
function sum(num1: number, num2: number): number {
return num1 + num2
}
匿名函数参数类型
在下面中,我们并没有指定item的类型,但是item是一个string类型
TS会根据forEach函数的类型以及数组的类型推断出item的类型- 这个过程称之为上下文类型(
contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型
// 通常情况下,在定义一个函数时,都会给参数加上类型注解
function foo(message: string) {
}
const names = ['abc', 'cba', 'nba']
// item根据上下文环境推导出来的,这个时候可以不添加类型注解
names.forEach(function (item) {
console.log(item.split(""));
})
可选类型
可选类型类似于这个参数是 类型 | undefined的联合类型
如果有必选类型,那么可选类型必须放到必选类型后面
一般的顺序:必传参数 - 有默认值参数 - 可选参数
function foo(x: number, y: number = 30, z?: number) {
console.log(x, y, z);
}
// undefined只有undefined类型能赋值,所以这里是使用默认参数30
foo(20)
foo(20, undefined)
foo(20, undefined, 40)
参数的对象类型
function printPoint(point: { x: number, y: number, z?: number }) {
console.log(point.x);
console.log(point.y);
console.log(point.z); // 如果没有传 输出undefined
}
printPoint({ x: 123, y: 321 })
printPoint({ x: 123, y: 321, z: 111 })
函数重载
在ts中实现函数重载,需要定义重载函数和具体实现函数,例子如下:
// 函数重载:函数的名称相同,但是参数不同的几个函数,就是函数的重载
// 定义函数重载
function add(num1: number, num2: number): number
function add(num1: string, num2: string): string
// 具体实现
function add(num1: any, num2: any): any {
if (typeof num1 === 'string' && typeof num2 === 'string') {
return num1.length + num2.length
}
return num1 + num2
}
// 匹配导number类型的函数重载
const result = add(20, 30)
const result2 = add('abc', 'cba')
console.log(result, result2);
// 在函数重载中,具体的实现函数不能直接被调用
// 报错 没有定义对象类型的函数重载 直接调用实现函数错误
// add({ name: 'zs' }, { age"18})
其他
联合类型
function printID(id: number | string) {
// 使用联合类型的时候 需要特别的小心
// console.log(id.toUpperCase()); // 报错
// narrow: 缩小 这里为缩小范围
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
printID(123)
printID('abc')
交叉类型
交叉类型&
交叉类型真正的用武之地就是将多个接口类型合并成一个类型,从而实现等同接口继承的效果,也就是所谓的合并接口类型
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}
类型别名
type用于定义类型别名,当然也可以使用interface接口
type IDType = string | number
type PointType = {
x: number,
y: number,
z?: number,
}
function printId(id: IDType) { }
function printPoint(point: PointType) { }
类型断言as
const el = document.getElementById('zs')
const el = document.getElementById('zs') as HTMLImageElement
// el获得的元素太广泛了 包含了很多的元素类型
// src是img里面的属性 在其他的HTMLElement中不一定有 所以会报错
// 所以我我们需要使用类型断言as来拿到HTMLImageElement 就不会报错了
el.src = 'url'
// 把string转换为number类型是不行的 需要把string先转为any或unknown类型
const message = 'Hello World'
// const num: number = (message as any) as number
const num: number = (message as unknown) as number
console.log(num);
案例
class Person { }
class Student extends Person {
studying() { }
}
function sayHello(p: Person) {
// p.studying() // 报错
(p as Student).studying()
}
const stu = new Student()
sayHello(stu)
非空类型断言
以下的编译是有问题的,因为message是可选的,当我们输入为空的时候,是拿不到length的
function printMessageLength(message?: string) {
console.log(message.length);
}
printMessageLength('Hello World')
printMessageLength()
怎么解决上面的问题呢?
使用if判断或者非空断言!,当然也可以使用ES11的可选链?.
function printMessageLength(message?: string) {
console.log(message!.length);
}
printMessageLength('Hello World')
printMessageLength()
字面量类型
// 当我们使用let的时候,TS会推导出类型注解
// 当使用const的时候,ts会把字面量直接作为类型注解
let message1 = 'Hello World' // string
const message2 = 'Hello TS' // Hello TS
// 字面量类型的意义,一般结合联合类型
type Alignment = 'left' | 'right' | 'center'
let align: Alignment = 'left'
align = 'right'
align = 'aaa' // 报错
字面量推理
type Method = 'GET' | 'POST'
function request(url: string, method: Method) { }
type Request = {
url: string,
method: Method
}
const options: Request = {
url: 'https://www.xxx',
method: 'POST'
}
// 如果在options中没有使用Request的话 options.method为字符串类型
// 或者在options.method后面添加as Method 这样就不需要在options后面添加类型注解了
// 也可以在options后面添加as const 这样里面的内容就是readonly
request(options.url, options.method)
export { }
类型缩小
28-1-46
类型缩小(Type Narrowing)
// 1.typeof的类型缩小
type IDType = number | string
function printID(id: IDType) {
console.log(id); // IDType
if (typeof id === 'string') {
console.log(id.toUpperCase()); // string
} else {
console.log(id); // number
}
}
// 2.平等的类型缩小 (=== == !== switch)
type Direction = 'left' | 'right' | 'top' | 'bottom'
function printDirection(direction: Direction) {
if (direction === 'left') {
console.log(direction); // left
} else {
}
// switch (direction) {
// case 'left':
// console.log(direction); // left
// break
// case ...
// }
}
// 3.instanceof
function printTime(time: string | Date) {
if (time instanceof Date) {
console.log(time.toUTCString);
} else {
console.log(time);
}
}
class Student {
studying() { }
}
class Teacher {
teaching() { }
}
function work(p: Student | Teacher) {
if (p instanceof Student) {
p.studying()
} else {
p.teaching()
}
}
const stu = new Student()
work(stu)
// 4.in
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
const fish: Fish = {
swimming() {
console.log('swimming');
}
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
animal.swimming()
} else {
animal.running()
}
}
TypeScript中的类
在TS里面需要在类里进行类型注解
class Person {
name: string
age: number = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + 'eating');
}
}
const p = new Person('zs', 19)
console.log(p.name);
p.eating()
继承
class Person {
name: string
age: number = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + 'eating 100行');
}
}
class Student extends Person {
sno: number
constructor(name: string, age: number, sno: number) {
super(name, age)
this.sno = sno
}
// 重写
eating(): void {
super.eating()
console.log('student eating');
}
studying() {
console.log('studying');
}
}
const s = new Student('zs', 19, 1)
console.log(s.name);
s.eating()
多态
class Animal {
action() {
console.log('animal running');
}
}
class Dog extends Animal {
action(): void {
console.log('dog running');
}
}
class Fish extends Animal {
action(): void {
console.log('fish swimming');
}
}
// 父类引用指向子类对象
// const animall: Animal=new Dog()
function makeActions(animals: Animal[]) {
animals.forEach(animal => {
animal.action()
})
}
makeActions([new Dog(), new Fish()])
成员修饰符
在TS中,类的属性和方法支持三种修饰符:public、private、protectec
public修饰的是在任何地方可见、共有的属性或方法,默认编写的属性就是publicprivate修饰的是仅在同一类可见、私有的属性或方法protected修饰的是仅在类自身及子类中可见、受保护的属性或方法
class Person {
protected name: string = 'zs'
}
class Student extends Person {
getName() {
return this.name
}
}
const stu = new Student()
console.log(stu.getName());
readonly
只读属性,不能修改
class Person {
readonly name: string
age?: number
readonly friend?: Person
constructor(name: string, friend?: Person) {
this.name = name
this.friend = friend
}
}
const p = new Person('zs', new Person('ls'))
console.log(p);
// friends是只读属性,不能修改
// 如果只读属性是一个对象,那么对象中的内容可以修改
// p.friend = new Person('ww')
if (p.friend) {
p.friend.age = 30
}
访问器
class Person {
private _name: string
constructor(name: string) {
this._name = name
}
set name(newName) {
this._name = newName
}
get name() {
return this._name
}
}
const p = new Person('zs')
p.name = 'ls'
console.log(p.name);
静态属性
静态属性与静态方法是使用static修饰的属性与方法
class Student {
static time: string = '8:00'
static attendClass() {
console.log('学习');
}
}
console.log(Student.time);
Student.attendClass()
抽象类
29-2
抽象类abstract:
- 抽象类不能实例化(被
new出来) - 继承抽象类的子类,必须实现抽象类中抽象的方法(实现多态)
function makeArea(shape: Shape) {
return shape.getArea()
}
abstract class Shape {
abstract getArea()
}
class Rectangle extends Shape {
private width: number
private height: number
constructor(width: number, height: number) {
super()
this.width = width
this.height = height
}
getArea() {
return this.width * this.height
}
}
class Circle extends Shape {
private r: number
constructor(r: number) {
super()
this.r = r
}
getArea() {
return this.r * this.r * 3.14
}
}
const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)
// const shape = new Shape() 错误
console.log(makeArea(rectangle));
console.log(makeArea(circle));
类的类型
类也是可以作为类型注解的
class Person {
name: string = '123'
eating() { }
}
// p1必须与Peron定义的属性key一致
const p1: Person = {
name: 'zs',
eating() { }
}
// 场景
function printPerson(p: Person) {
console.log(p.name);
}
// 传入参数的两种方法
printPerson(new Person())
printPerson({ name: 'ls', eating: function () { } })
TypeScript接口
声明对象类型
一种声明对象类型方式:接口interiface
// 通过type别名来声明对象类型
// type InfoType = { name: string, age: number }
// 另外一种声明对象类型方式:接口interface
// 第一个I为接口的意思 这是一个规范
interface IInfoType {
readonly name: string
age: number
friends?: { name: string }
}
const info: IInfoType = {
name: 'zs',
age: 18
}
索引类型
interface IndexLanguage {
[index: number]: string
}
// key为number类型 value为string类型
const frontLanguage: IndexLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
4: "Vue"
}
interface ILanguageYear {
[name: string]: number
}
const languageYear: ILanguageYear = {
"C": 1972,
"Java": 1995,
"JavaScript": 1996,
"TypeScript": 2014,
}
函数类型
接口的实现
使用implements关键字可以实现一个或多个接口
interface ISwim {
swimming: () => void
}
interface IEat {
eating: () => void
}
class Animal {
}
// 继承:只能实现单继承
// 实现:实现接口,类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
swimming() {
console.log("Fish Swimming");
}
eating() {
console.log("Fish Eating");
}
}
class Person implements ISwim {
swimming() {
console.log("Person Swimming");
}
}
function swimAction(swimable: ISwim) {
swimable.swimming()
}
swimAction(new Fish())
swimAction(new Person())
swimAction({ swimming: function () { } })
interface与type区别
我们发现interface与type都可以用来定义对象类型,那么在开发中选择哪一个呢?
- 如果是定义非对象类型,通常使用
type,比如Direction、Alignment或者一些Function
如果定义对象类型,那么它们是又区别的
-
interface可以重复定义某个接口来定义属性和方法// interface可以重复定义且它们会合并 interface IFoo { name: string } interface IFoo { age: number } // 相当于 // interface IFoo { // name: string, // age: number // } const foo: IFoo = { name: 'zs', age: 18 } // 会与window进行合并 window中多了给age属性 interface Window { age: number } window.age = 20 console.log(window.age); -
而
type定义的是别名,别名不能重复// type不能重复定义 type IBar = { name: string } type IBar = { // 报错 age: number }字面量赋值
擦除操作
freshness,示例如下:interface IPerson { name: string age: number height: number } const info = { name: 'zs', age: 18, height: 1.88, address: '广州市' } function printInfo(person: IPerson) { console.log(person); // console.log(person.address); 报错 } // 报错 // printInfo({ // name: 'zs', // age: 18, // height: 1.88, // address: '广州市' // }) // 在这里ts会进行擦除操作 在虽然把info传进去了 但是不能拿到info中的address printInfo(info) // 这里也进行了擦除操作 const p: IPerson = info console.log(info); console.log(p);
TypeScript枚举类型
// 枚举右默认值 默认等于0,往后递增
// 枚举的值是可以修改的
enum Direction {
LEFT = 100, // 默认为0 修改后后面的值会递增 RIGHT=101 ... 也可以改成字符串
RIGHT,
TOP,
BOTTOM
}
function turnDirection(direction: Direction) {
console.log(direction);
switch (direction) {
case Direction.LEFT:
console.log('改变角色的方向向左');
break
case Direction.RIGHT:
console.log('改变角色的方向向右');
break
case Direction.TOP:
console.log('改变角色的方向向上');
break
case Direction.BOTTOM:
console.log('改变角色的方向向下');
break
default:
const foo: never = direction
break
}
}
turnDirection(Direction.LEFT)
泛型
泛型就是类型参数化,例子如下
function sum<Type>(num1: Type): Type {
return num1
}
// 1.调用方式一:明确传入类型
console.log(sum<number>(20));
console.log(sum<{ name: string }>({ name: 'zs' }));
console.log(sum<any[]>(['abc']));
// 2.调用方式二:类型推导
console.log(sum(50));
console.log(sum('abc'));
多个参数
// 剩余参数只能使用前面类型中的一个
function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {
}
foo<number, string, boolean>(10, 'abc', true, 10, 20, 30)
// 类型推导
foo(10, 'abc', true, 10, 20, 30)
泛型接口
interface IPerson<T1, T2> {
name: T1
age: T2
}
const p: IPerson<string, number> = {
name: 'zs',
age: 18
}
泛型类
class Point<T>{
x: T
y: T
z: T
constructor(x: T, y: T, z: T) {
this.x = x
this.y = y
this.z = z
}
}
const p1 = new Point<string>('1', '2', '3')
const p2: Point<string> = new Point('1', '2', '3')
// 类型推导
const p3 = new Point('123', '234', '345')
const names1: string[] = ['a', 'b', 'c']
const names2: Array<string> = ['a', 'b', 'c'] // 不推荐 因为可能会与jsx冲突
类型约束
在TS中可以使用extends对泛型的类型进行约束
interface ILength {
length: number
}
// 传递的类型需要有length这个属性
function getLength<T extends ILength>(arg: T) {
return arg.length
}
console.log(getLength('abc'));
console.log(['a', 'b']);
console.log({ length: 100 });
模块化开发
TS支持两种方式来控制我们的作用域
-
模块化:每个文件可以是独立的模块,支持
ES Module,也支持CommonJS导出
export function add(num1: number, num2: number) { return num1 + num2 } export function sub(num1: number, num2: number) { return num1 - num2 }导入
import { add, sub } from './utils/math'; console.log(add(20, 30)); console.log(sub(20, 30)); -
命名空间:通过
namespace来声明一个命名空间- 同一模块中,命名空间中的内容需要导出,不然在外面拿不到
- 在其他模块引用,需要把命名空间导出
导出
export namespace time { export function format(time: string) { return '2222-2-22' } function foo() { } export let name: string = 'zs' } namespace price { export function format(price: number) { return '99.99' } } // 命名空间中的内容需要导出 不然在外面拿不到 console.log(time.format('2022')); time.name // time.foo // 错误导入
import { time } from './utils/format' console.log(time.name);
类型查找
30-2-20
之前我们所有的ts中的类型,都是我们自己编写的,但是我们也有用到其他的一些类型
const el = document.getElementById('zs') as HTMLImageElement
那么HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?
- 其实这里就涉及到
ts对类型的管理和查找规则了
我们来认识一种ts文件:.d.ts文件
- 我们编写的
ts文件都会输出为.js文件 .d.ts文件,它是用来做类型的声明(declare),它仅仅用来做类型检测,告知ts有那些类型
那么ts会在哪里查找我们的类型声明呢?
- 内置类型声明
- 外部定义类型声明
- 自己定义类型声明
内置类型声明
内置类型声明是ts自带的、帮助我们内置了JS运行时的一些标准化API声明文件
- 包括
Math、Date等内置类型,也包括DOM API,比如Window、Document等
内置类型说明通常在我们安装ts的环境会自带:
外部定义类型声明
外部类型什么通常是我们使用一些库(比如第三方库)时,需要的一些类型声明
这是库通常有两种类型声明方式:
-
在中间库中进行类型声明(编写
.d.ts文件),比如axios -
通过社区的一个公有库
Definitely Typed存放类型声明文件-
该库的
GitHub地址:github.com/DefinitelyT… -
该库查找声明安装方式的地址:www.typescriptlang.org/dt/search?s…
-
比如我们安装
react的类型声明与lodash的类型声明:npm i @types/react -D导入
loadshimport lodash from 'lodash'; // 会报错 // 安装后 不报错 npm install @types/lodash -D
-
自定义类型声明
30-2-44
如果我们没有内置类型声明,也没有外部定义类型声明,那么我们该怎么办?
可以使用自定义类型声明
创建一个declare.d.ts文件,这个文件只是用来定义类型,不用赋值
// 声明模块
declare module 'lodash' {
export function join(arr: any[])
}
// 声明函数/变量/类
declare let name: string
declare let age: number
declare function foo(): void
declare class Person {
name: string
age: number
constructor(name: string, age: number)
}
// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
declare module '*.vue' { }
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
index.html
<script>
let name = 'zs'
let age = 18
function foo() {
console.log('foo');
}
function Person(name, age) {
this.name = name
this.age = age
}
</script>
这样引入就不会有问题了
import lodash from 'lodash';
// 在TS中直接引入文件也出错的 需要在.d.ts中声明
import img from './img/sunset_wide.png';
console.log(lodash.join(['abc', 'cba']));
// 这里能输出name与age 因为在html中定义有 这里的ts代码最终会被转为js代码放到html中的script中
console.log(name);
console.log(age);
foo()
const p = new Person('ls', 20)
console.log(p);
$.ajax({})