TypeScript 是 Javascript的超集,它可以编译成纯 Javascript。
Typescript 可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的
# 安装命令
npm install typescript -g
# 查看版本
tsc --version
通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js',
},
resolve: {
extensions: ['.ts', '.js', '.cjs', '.json'],
},
devServer: {},
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
};
通过ts-node库,为TypeScript的运行提供执行环境
// 安装ts-node
npm install ts-node -g
// ts-node需要依赖 tslib 和 @types/node 两个包:
npm install tslib @types/node -g
// 通过 ts-node 来运行TypeScript的代码
ts-node math.ts
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解;
var/let/const 标识符: 数据类型 = 赋值;
let message: string = 'hello word'
const arr1: string[] = ['name']
const arr1: Array[string] = ['name']
const person: object = {
name: '张三',
age: 18
}
const s1: symbol = Symbol('title')
const s2: symbol = Symbol('title')
const person = {
[s1]: 程序员,
[s2]: 程老师
}
在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型
const n: null = null
const u: undefined = undefined
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型
any类型有点像一种讨巧的TypeScript手段
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型
never 表示永远不会发生值的类型
function loopFn():never {
while(true) {
console.log('123')
}
}
function loopError():never {
throw new Error('123')
}
// state 0: 失败 1: 成功
function handleState(state: number) {
switch(state) {
case 0: return '失败';
case 1 :return '成功';
default:
return const msg:never = "不可能到这一步"
}
}
const useInfo: [string, number, number] = ['name', 18, 1.88]
tuple通常可以作为返回的值,在使用的时候会非常的方便;
function useState<T>(state: T): [T, (newState: T) => void] {
let currentState = state
const changeState = (newState) => {
currentState = newState
}
return [currentState, changeState ]
}
const [currentState, changeState] = useState(10)
enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM
}
function turnDirection(direction: 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)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
enum Direction {
LEFT = "LEFT",
RIGHT = "RIGHT",
TOP = "TOP",
BOTTOM = "BOTTOM"
}
let name: string = "abc"
let d: Direction = Direction.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)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)
n 在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进行传递)。
我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型
// 1.函数作为参数时, 在参数中如何编写类型
function foo() {}
type FooFnType = () => void
function bar(fn: FooFnType) {
fn()
}
bar(foo)
// 2.定义常量时, 编写函数的类型
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2
}
从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的
// 必传参数 - 有默认值的参数 - 可选参数
function foo(y: number, x: number = 20, z?: number) {
console.log(x, y)
}
foo(30)
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中
function sum(initalNum: number, ...nums: number[]) {
let total = initalNum
for (const num of nums) {
total += num
}
return total
}
console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
function getUseInfo(name: string) {
return name
}
如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
function printCoordinate(point: {x: number, y: number}): void {
console.log('x坐标', point.x)
console.log('y坐标', point.y)
}
printCoordinate({x: 10, y: 30})
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?
可选类型需要在必传参数的后面
function printCoordinate(point: {x: number, y: number, z?: number}): void {
console.log('x坐标', point.x)
console.log('y坐标', point.y)
}
printCoordinate({x: 10, y: 30})
printCoordinate({x: 10, y: 30, z: 40})
补充
可选类型可以看做是 类型 和 undefined 的联合类型
function printId(id: number | string) {
console.log('你的id是:', id)
}
printId(10)
printId('string')
在 TypeScript 中,检查返回的值typeof是一种类型保护:因为 TypeScript 对如何typeof操作不同的值进行编码。
type IDType = number | string
function printID(id: IDType) {
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
我们可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )
type Direction = "left" | "right" | "top" | "bottom"
function printDirection(direction: Direction) {
// 1.if判断
// if (direction === 'left') {
// console.log(direction)
// } else if ()
// 2.switch判断
// switch (direction) {
// case 'left':
// console.log(direction)
// break;
// case ...
// }
}
JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”
function printTime(time: string | Date) {
if (time instanceof Date) {
console.log(time.toUTCString())
} else {
console.log(time)
}
}
Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
class Student {
studying() {}
}
class Teacher {
teaching() {}
}
function work(p: Student | Teacher) {
if (p instanceof Student) {
p.studying()
} else {
p.teaching()
}
}
interface ISwim {
swimming: () => void
}
interface IFly {
flying: () => void
}
type MyType1 = ISwim | IFly
type MyType2 = ISwim & IFly
const obj1: MyType1 = {
flying() {
}
}
const obj2: MyType2 = {
swimming() {
},
flying() {
}
}
function num (num1: number, num2: number): number {
return number
}
和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型
const names: string[] = ['张三', '李四', '王五']
names.forEach(item => console.log(item))
// this是可以被推导出来 info对象(TypeScript推导出来)
const info = {
name: "why",
eating() {
console.log(this.name + " eating")
}
}
info.eating()
export {}
type ThisType = { name: string };
function eating(message: string) {
console.log(this.name + " eating", message);
}
const info = {
name: "why",
eating: eating,
};
// 隐式绑定
info.eating("哈哈哈");
type ThisType = { name: string };
function eating(this: ThisType, message: string) {
console.log(this.name + " eating", message);
}
const info = {
name: "why",
eating: eating,
};
// 隐式绑定
info.eating("哈哈哈");
// 显示绑定
eating.call({name: "kobe"}, "呵呵呵")
eating.apply({name: "james"}, ["嘿嘿嘿"])
/**
* 通过联合类型有两个缺点:
* 1.进行很多的逻辑判断(类型缩小)
* 2.返回值的类型依然是不能确定
*/
function add(a1: number | string, a2: number | string) {
if (typeof a1 === "number" && typeof a2 === "number") {
return a1 + a2
} else if (typeof a1 === "string" && typeof a2 === "string") {
return a1 + a2
}
}
add(10, 20)
// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载
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
}
const result = add(20, 30)
const result2 = add("abc", "cba")
console.log(result)
console.log(result2)
// "Hello World"也是可以作为类型的, 叫做字面量类型
const message: "Hello World" = "Hello World"
// 字面量类型的意义, 就是必须结合联合类型
type Alignment = 'left' | 'right' | 'center'
let align: Alignment = 'left'
align = 'right'
align = 'center'
这是因为我们的对象再进行字面量推理的时候,info其实是一个 {url: string, method: string},所以我们没办法将一个 string 赋值给一个 字面量 类型。
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {}
const options = {
url: "https://www.coderwhy.org/abc",
method: "POST"
} as const
request(options.url, options.method)
const el = document.getElementById("why") as HTMLImageElement
el.src = "url地址"
const message = "Hello World"
const num: number = (message as unknown) as number
function printMessageLength(message?: string) {
console.log(message!.length)
}
printMessageLength("hello world")
type Person = {
name: string
friend?: {
name: string
age?: number,
girlFriend?: {
name: string
}
}
}
const info: Person = {
name: "why",
friend: {
name: "kobe",
girlFriend: {
name: "lily"
}
}
}
console.log(info.name)
console.log(info.friend?.name)
console.log(info.friend?.age)
console.log(info.friend?.girlFriend?.name)
const message = "Hello World"
const flag = !!message
console.log(flag) // true
let message: string|null = "Hello World"
const content = message ?? "你好啊"
// const content = message ? message: "你好啊"
console.log(content)
在早期的JavaScript开发中(ES5)我们需要通过函数和原型链来实现类和继承,从ES6开始,引入了class关键字,可以 更加方便的定义和使用类
TypeScript作为JavaScript的超集,也是支持使用class关键字的,并且还可以对类的属性和方法等进行静态类型检测。
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + " eating")
}
}
const p = new Person("why", 18)
console.log(p.name)
console.log(p.age)
p.eating()
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log("eating 100行")
}
}
class Student extends Person {
sno: number
constructor(name: string, age: number, sno: number) {
// super调用父类的构造器
super(name, age)
this.sno = sno
}
eating() {
console.log("student eating")
super.eating()
}
studying() {
console.log("studying")
}
}
const stu = new Student("why", 18, 111)
console.log(stu.name)
console.log(stu.age)
console.log(stu.sno)
stu.eating()
class Person {
// 1.只读属性是可以在构造器中赋值, 赋值之后就不可以修改
// 2.属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改
readonly name: string
age?: number
readonly friend?: Person
constructor(name: string, friend?: Person) {
this.name = name
this.friend = friend
}
}
const p = new Person("why", new Person("kobe"))
console.log(p.name)
console.log(p.friend)
// 不可以直接修改friend
// p.friend = new Person("james")
if (p.friend) {
p.friend.age = 30
}
// p.name = "123"
在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。
class Person {
private _name: string
constructor(name: string) {
this._name = name
}
// 访问器setter/getter
// setter
set name(newName) {
this._name = newName
}
// getter
get name() {
return this._name
}
}
const p = new Person("why")
p.name = "coderwhy"
console.log(p.name)
class Student {
static time: string = "20:00"
static attendClass() {
console.log("去学习~")
}
}
console.log(Student.time)
Student.attendClass()
function makeArea(shape: Shape) {
return shape.getArea()
}
abstract class Shape {
abstract getArea(): number
}
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)
console.log(makeArea(rectangle))
console.log(makeArea(circle))
class Person {
name: string = "123"
eating() {
}
}
const p = new Person()
const p1: Person = {
name: "why",
eating() {
}
}
function printPerson(p: Person) {
console.log(p.name)
}
printPerson(new Person())
printPerson({name: "kobe", eating: function() {}})
type ID = number | string
function printId(id: ID) {
console.log('你的id是:', id)
}
printId(10)
// 通过类型(type)别名来声明对象类型
// type InfoType = {name: string, age: number}
// 另外一种方式声明对象类型: 接口interface
// 在其中可以定义可选类型
// 也可以定义只读属性
interface IInfoType {
readonly name: string
age: number
friend?: {
name: string
}
}
const info: IInfoType = {
name: "why",
age: 18,
friend: {
name: "kobe"
}
}
console.log(info.friend?.name)
console.log(info.name)
// info.name = "123"
info.age = 20
// 通过interface来定义索引类型
interface IndexLanguage {
[index: number]: string
}
const frontLanguage: IndexLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue"
}
interface ILanguageYear {
[name: string]: number
}
const languageYear: ILanguageYear = {
"C": 1972,
"Java": 1995,
"JavaScript": 1996,
"TypeScript": 2014
}
// type CalcFn = (n1: number, n2: number) => number
// 可调用的接口
interface CalcFn {
(n1: number, n2: number): number
}
function calc(num1: number, num2: number, calcFn: CalcFn) {
return calcFn(num1, num2)
}
const add: CalcFn = (num1, num2) => {
return num1 + num2
}
calc(20, 30, add)
interface ISwim {
swimming: () => void
}
interface IFly {
flying: () => void
}
interface IAction extends ISwim, IFly {
}
const action: IAction = {
swimming() {
},
flying() {
}
}
interface ISwim {
swimming: () => void
}
interface IRun {
running: () => void
}
class Person implements ISwim,IRun {
swimming() {
console.log('swimming')
},
running() {
console.log('running')
}
}
function swim(swimmer: ISwim) {
swimmer.swimming()
}
const p = new Person()
swim(p)
interface IFoo {
name: string
}
interface IFoo {
age: number
}
const foo: IFoo = {
name: "why",
age: 18
}
type IBar = {
name: string
age: number
}
// type IBar = {
// }
interface IPerson {
name: string
age: number
height: number
}
function printInfo(person: IPerson) {
console.log(person)
}
const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
printInfo(info)
function sum<Type>(num: Type): Type {
return num
}
sum<number>(20)
sum<{name: string}>({name: "why"})
sum<any[]>(["abc"])
sum(50)
sum("abc")
function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {
}
foo<number, string, boolean>(10, "abc", true)
interface IPerson<T1 = string, T2 = number> {
name: T1
age: T2
}
const p: IPerson = {
name: "why",
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 = y
}
}
const p1 = new Point("1.33.2", "2.22.3", "4.22.1")
const p2 = new Point<string>("1.33.2", "2.22.3", "4.22.1")
const p3: Point<string> = new Point("1.33.2", "2.22.3", "4.22.1")
interface ILength {
length: number
}
function getLength<T extends ILength>(arg: T) {
return arg.length
}
getLength("abc")
getLength(["abc", "cba"])
getLength({length: 100})
命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题。
export namespace time {
export function format(time: string) {
return "2222-02-22"
}
export function foo() {
}
export let name: string = "abc"
}
const imageEl = document.getElementById('images') as HTMLImageElement
// 声明变量/函数/类
declare let whyName: string
declare let whyAge: number
declare let whyHeight: number
declare function whyFoo(): void
declare class Person {
name: string
age: number
constructor(name: string, age: number)
}
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
// 声明模块
declare module 'lodash' {
export function join(arr: any[]): void
}
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
$.ajax({})