需要安装的依赖
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.1.0",
"clean-webpack-plugin": "^3.0.0",
"eslint": "^7.2.0",
"html-webpack-plugin": "^4.3.0",
"ts-loader": "^7.0.5",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
}
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: './src/index.ts',
output: {
filename: 'main.js',
path: path.resolve(__dirname, '../dist'),
},
resolve: {
extensions: ['.js', '.ts', '.tsx'],
},
module: {
rules: [
{
oneOf: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
],
},
devtool:
process.env.NODE_ENV === 'development'
? 'eval-source-map'
: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './src/template/index.html',
}),
new CleanWebpackPlugin(),
],
devServer: {
contentBase: '../dist',
compress: true,
port: 8090,
open: true,
},
}
// eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
extends: ['plugin:@typescript-eslint/recommended'],
plugins: ['@typescript-eslint'],
rules: {},
// webstorm老是会去lint这俩文件,然后报错,所以加入忽略
ignorePatterns: ['webpack.config.js', '.eslintrc.js'],
}
tsc --init # 初始化tsconfig.json
package.json 添加脚本命令
"scripts": {
"dev": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
"build": "webpack --mode=production --config ./build/webpack.config.js",
"lint": "eslint src --ext .js,.ts --fix"
},
boolean
number(支持二进制0b1101
,八进制0o173
,十六进制0x7b
)
string
数组:
元组类型
let tuple:[string,number,boolean] = ['a',1,false] 长度和元素类型顺序都有要求
枚举类型
enum Users {
CREATOR, // 0
ADMIN, // 1
USER = 3, // 3 可以自行分配
}
console.log(Users.CREATOR)
console.log(Users[3]) // USER
any 类型
void 类型 (undefined,在非 strict 模式下可以是 null)
const consoleText = (txt: string): void => {
console.log(txt)
// 因为没有返回值,声明返回类型为void
}
consoleText('123')
undefined
null
never 类型
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}
// 推断的返回值类型为never
function fail() {
return error('Something failed')
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {}
}
const neverVal = (): never => {
throw new Error('abc')
}
myStr = neverVal() // never类型可以赋值给任意类型,但是反过来不可以
object(eslint 不推荐使用)
function printObj(obj: object): void {
console.log(obj)
}
printObj({
name: 'Samuel',
})
// 类型断言
const getLength = (target: string | number): number => {
/*
在这个if里面我们知道target就是string类型而不是number,但是ts编译器不知道
所以, 需要类型断言告诉编译器这里的target是string类型的
*/
if ((<string>target).length || (target as string).length === 0) {
return (<string>target).length
} else {
return target.toString().length
}
}
getLength(123)
类型断言有两种写法
(<类型>变量)
(变量 as 类型)
React 只能这么写,因为<>会被当成标签
Symbols 是不可改变且唯一的。主要用法是用来做对象的属性名,这样可以保证属性名独一无二不被 mixin 覆盖
// 没有参数的情况
let s1 = Symbol()
let s2 = Symbol()
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo')
let s2 = Symbol('foo')
s1 === s2 // false
Symbol 不可以和其他类型的值做运算,但是有 toString 方法,且可以转换成布尔
let sym = Symbol('My symbol')
'your symbol is ' + sym
// TypeError: can't convert symbol to string
1 + sym
// TypeError
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
Boolean(sym) // true
!sym // false
Symbol 可以用来解决强耦合的某一个具体的字符串或数字
function getArea(shape, options) {
let area = 0
switch (shape) {
case 'Triangle': // 魔术字符串(强耦合)
area = 0.5 * options.width * options.height
break
/* ... more code ... */
}
return area
}
// 可以定义一个对象来指定case类型
const shapeType = {
triangle: 'Triangle',
}
function getArea(shape, options) {
let area = 0
switch (shape) {
case shapeType.triangle: // 解耦
area = 0.5 * options.width * options.height
break
}
return area
}
// 其实triangle是什么字符串不重要,因此可以使用Symbol
const shapeType = {
triangle: Symbol(),
}
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
const nameSymbol = Symbol('name')
const obj = {
[nameSymbol]: 'Samuel',
age: 18,
}
for (const key in obj) {
console.log(key) // age
}
console.log(Object.keys(obj)) // Array["age"]
console.log(Object.getOwnPropertyNames(obj)) // Array["age"]
但是可以被Object.getOwnPropertySymbols()
和Reflect.ownKeys()
获取
console.log(Object.getOwnPropertySymbols(obj)) // Array [ Symbol(name) ]
有时,我们希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
s1 === s2 // true
Symbol.keyFor()
会返回一个使用Symbol.for(key)
登记过的 key
let s1 = Symbol.for('foo')
Symbol.keyFor(s1) // "foo"
let s2 = Symbol('foo')
Symbol.keyFor(s2) // undefined
对象的Symbol.hasInstance
属性,指向一个内部方法。当其他对象使用instanceof
运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo
在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
。
// es6 js, 非ts
class MyClass {
[Symbol.hasInstance](foo) {
console.log(foo)
}
}
;[1, 2, 3] instanceof new MyClass()
对象的Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开。
let arr1 = ['c', 'd']
;['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd']
arr2[Symbol.isConcatSpreadable] = false
;['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
对象的Symbol.species
属性,指向一个构造函数。创建衍生对象时,会使用该属性。
class MyArray extends Array {}
const a = new MyArray(1, 2, 3)
const b = a.map((x) => x)
const c = a.filter((x) => x > 1)
b instanceof MyArray // true
c instanceof MyArray // true
子类MyArray
继承了父类Array
,a
是MyArray
的实例,b
和c
是a
的衍生对象。你可能会认为,b
和c
都是调用数组方法生成的,所以应该是数组(Array
的实例),但实际上它们也是MyArray
的实例。
class MyArray extends Array {
static get [Symbol.species]() {
return Array
}
}
const a = new MyArray()
const b = a.map((x) => x)
b instanceof MyArray // false
b instanceof Array // true
上面代码中,a.map(x => x)
生成的衍生对象,就不是MyArray
的实例,而直接就是Array
的实例。
String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)
对象的Symbol.iterator
属性,指向该对象的默认遍历器方法。
const myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
}
;[...myIterable] // [1, 2, 3]
myIterable.next()
当对象转换为基本类型时调用这个方法
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123
case 'string':
return 'str'
case 'default':
return 'default'
default:
throw new Error()
}
},
}
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
对象的Symbol.toStringTag
属性,指向一个方法。这个属性可以用来定制 Object.prototype.toString.call(obj)的返回的[object Object]
或[object Array]
中object
后面的那个字符串。
class Collection {
get [Symbol.toStringTag]() {
return 'xxx'
}
}
let x = new Collection()
Object.prototype.toString.call(x) // "[object xxx]"
使用接口限制对象的属性和属性类型
// 使用接口限制属性类型
interface NameInterface {
firstName: string
lastName: string
}
// 限制对象作为参数传递时属性的类型
function getFullname({ firstName, lastName }: NameInterface): string {
return `${firstName}·${lastName}`
}
console.log(
getFullname({
firstName: 'Samuel',
lastName: 'Sue',
})
)
可选属性的限制
// 对于可选参数接口
interface VegetableInterface {
color?: string // 可以有color也可以没有
type: string
}
function VegInfo({ color, type }: VegetableInterface): string {
return `${color || ''}${type}`
}
console.log(VegInfo({ type: 'Onion' }))
对于多余属性,有三种方法解除属性限制
enum Gender {
Female,
Male,
}
// 对于多余属性
interface PersonInfo {
name: string
age: number
gender: Gender
// 多余属性解决方法1, 定义其他属性
[props: string]: any
}
function logPerson({ name, age, gender }: PersonInfo): void {
console.log(`Person:${name},${age} years old,${gender}`)
}
logPerson({
name: 'Samuel',
age: 25,
gender: Gender.Male,
hobby: 'Basketball', // 可以传递多余属性
})
// ==========================================
logPerson({
name: 'Samuel',
age: 25,
gender: Gender.Male,
hobby: 'Basketball', // 可以传递多余属性
} as PersonInfo) // 多余属性解决方法2,类型断言
// ===========================================
// 多余属性解决方法3 类型兼容性
const person = {
name: 'Samuel',
age: 25,
gender: Gender.Male,
hobby: 'Basketball', // 可以传递多余属性
}
logPerson(person)
注意,剩余属性(属性索引)是规定了其他属性的类型的,举个例子:
interface Role {
[prop: string]: number // 这就规定了Role接口描述的对象的所有属性的值都必须是number
// name: string, // 这就报错了,因为name属性的值是string类型的,没法赋值给number类型.
}
// 只读属性
interface ArrInterface {
0: number // 0位置只能是number
readonly 1: string // 1位置只读,不可改
}
const arr: ArrInterface = [123, 'hahah']
// arr[1] = 'good' // Cannot assign to '1' because it is a read-only property.
// 属性名如果是number类型,会自动转换成string
interface RoleDict {
[id: string]: number
}
const role: RoleDict = {
12: 123, // 12实际上是转换成了string
// '12':234 // 报 属性重复 的错误
}
console.log(role['12'])
// 接口继承
interface Tomato extends VegetableInterface {
radius: number
}
函数接口: 注意函数类型写法(arg:string,arg2:number):void
,即(参数:类型...):返回值类型
// 计数器函数接口
interface CounterInterface {
(): void // 函数类型属性, 参数为空,无返回值
counter: number
}
const countFn = (): CounterInterface => {
const fn = function () {
fn.counter++
}
fn.counter = 0
return fn
}
const countFunc: CounterInterface = countFn()
countFunc()
console.log(countFunc.counter)
countFunc()
console.log(countFunc.counter)
// 接口来定义函数类型(lint推荐使用类型别名来定义)
interface AddFnInterface {
(x: number, y: number): number // 函数类型属性
}
// 类型别名定义函数类型
type Add = (x: number, y: number) => number
const addFunc: Add = (x: number, y: number) => x + y
// 函数可选参数,默认参数
type AddFnType = (x: number, y?: number) => number
const fn2: AddFnType = (x = 3, y) => x + (y || 0)
// 剩余参数
type SumFnType = (...args: number[]) => number
const fn3: SumFnType = (...args) =>
args.reduce((previousValue, currentValue) => previousValue + currentValue, 0)
console.log(fn3(1, 2, 3, 4, 5, 6))
// 函数重载(ts的重载只是用于代码提示,不是真正意义上的重载)
function splitChar(x: number): string[]
function splitChar(x: string): string[]
function splitChar(x: any): any {
// 前两个是方法声明(支持重载类型), 这里才是方法实体(必须这么写)
if (typeof x === 'string') {
return x.split('')
} else {
return (x as number).toString().split('')
}
}
console.log(splitChar(123456))
console.log(splitChar('我是爸爸'))
对于函数,传入的参数类型事前无法确定,在调用时确定,且返回值类型也需要根据参数类型确定,同时对于这种动态的写法我们有时仍需要对类型做判断以调用不同类型对象的方法。
这时候我们需要的就是泛型
// (函数)变量类型泛型
const genArr = <T>(val: T, times: number): T[] => {
return new Array(times).fill(val)
}
// 函数类型别名泛型
type GenArr = <T>(val: T, times: number) => T[]
const genFn: GenArr = (val, times) => {
return new Array(times).fill(val)
}
// genArr(123,3).map(item=>item.length) // Property 'length' does not exist on type 'number'.
genArr(123, 3).map((item) => item.toFixed())
console.log(genArr('爸爸', 3).map((item) => item.split(''))) // 根据val类型, 可以提示响应类型的方法
// 泛型约束, 假如只允许带length属性的类型传入
interface WithLength {
length: number
}
const genFn2 = <T extends WithLength>(val: T, times: number): T[] => {
return new Array(times).fill(val)
}
genFn2([1, 2, 3], 2)
genFn2('爸爸', 2)
// genFn2(123,2) //Argument of type '123' is not assignable to parameter of type 'WithLength
interface WithLength {
length: number
}
// 这里T extends WithLength, 理解上就是泛型类型T=WithLength(type T = WithLength)
const genFn2 = <T extends WithLength>(val: T, times: number): T[] => {
return new Array(times).fill(val)
}
interface Person {
name: string
age: number
gender: string
}
class Teacher {
constructor(private info: Person) {}
/*
keyof Person就是:'name','age','gender',
T extends 'name':等价与 type T = 'name'
...
T类型就是属性名的联合类型。
所以这里参数key就是属性名。 Person[T]就是属性值类型
*/
getInfo<T extends keyof Person>(key: T): Person[T] {
return this.info[T]
}
}
// 泛型约束, 只允许访问对象存在的属性
function showProp<T, K extends keyof T>(obj: T, prop: K) {
return obj[prop]
}
const person1 = {
name: 'Samuel',
age: 26,
gender: 'Male',
}
console.log(showProp(person1, 'age'))
// console.log(showProp(person1,"addr")) //Argument of type '"addr"' is not assignable to parameter of type '"age" | "name" | "gender"'.
补充知识点:
_
,真正意义上的私有化)class Parent{
constuctor(){
if(new.target===Parent){
throw new Error('父类不允许实例化')
}
}
}
let privateSymbol = new Symbol('name')
class Child extends Parent{
constructor(name){
super()
this[privateSymbol] = name
console.log(new.target)
}
getName(){
return this[privateSymbol]
}
}
export Child // 只导出Child类,不导出privateSymbol,外部无法直接访问
// new Parent // throw Error
new Child() // 打印Child的构造方法(也就是这个类)
class Parent {
constructor() {
this.name = 'Parent'
}
showName() {
return this.name
}
static getName() {
return Parent.name
}
}
class Child extends Parent {
constructor() {
super() // 指向父类构造方法
this.name = 'Child'
}
showParentName() {
return super.showName() // 成员方法中super指向父类实例(但是this是指向自己的)
}
static getParentName() {
return super.getName() // 静态方法中super指向父类静态类
}
}
Child.getParentName() // 'Parent'
new Child().getParentName() // 'Parent' super指向父类实例(但是this是指向自己的)
class Parent {
protected x: number
// 构造方法声明protect, 只能在派生类中被super执行
protected constructor(x: number) {
this.x = x
}
protected getX() {
return this.x
}
}
class Child extends Parent {
constructor(x: number) {
super(x) //
}
getX(): number {
// console.log(super.x) //子类内部只能通过super访问父类的protect方法,不能访问protect属性
return super.getX()
}
}
// const p = new Parent(123)
const ch = new Child(234)
console.log(ch.getX())
// 只读属性
class ReadOnlyInfo {
public readonly info: string
constructor(info: string) {
this.info = info
}
}
const rInfo = new ReadOnlyInfo('只能读,不能改')
// rInfo.info = '改一个试试' // Cannot assign to 'info' because it is a read-only property.
console.log(rInfo)
// 参数修饰符(简写)
class A {
constructor(private name: string) {
// 使用参数修饰符可以自动帮你把参数绑定到this上
}
}
const a = new A('Samuel')
console.log(a) // {name:'Samuel'}
// 静态成员属性
class B {
public static readonly pi = 3.1415926
private static num = 2333
}
console.log(B.pi)
// B.pi = 3.14
// console.log(B.num)
// 可选属性/可选参数 set/get
class Human {
private _name: string
public age?: number
constructor(name: string, age?: number, public gender?: string) {
this._name = name
this.age = age
}
public set name(newVal: string) {
this._name = newVal
}
public get name() {
return this._name.replace(/./, (substring) => substring.toUpperCase())
}
}
const h = new Human('samuel')
h.name = 'zhangboy'
console.log(h.name)
// 抽象类
abstract class MyFile {
protected constructor(public size: number) {}
public abstract save(): void // 抽象函数,没有函数体
}
class MediaFile extends MyFile {
constructor(public size: number, public type: string) {
super(size) // 必须执行父类(抽象类)构造函数
}
save(): void {
console.log('save media file')
}
play(): void {
console.log('play media File')
}
}
// 类实现接口
interface player {
supportType: string[]
play: () => void
}
class MusicPlayer implements player {
play(): void {
console.log('play music')
}
supportType: string[]
constructor(...types: string[]) {
this.supportType = types
}
}
const player = new MusicPlayer('mp3', 'wav', 'wma', 'acc')
player.play()
// 接口继承类
class Point {
protected x: number // 因为是protect,因此只能在内部或者派生类中初始化
protected y: number
protected constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
interface Point3d extends Point {
z: number
}
// 因为要初始化protected x,y 所以要继承类
class AcPoint extends Point implements Point3d {
z: number
constructor(x: number, y: number, z: number) {
super(x, y)
this.z = 312
}
}
/*
假如我们要限制参数是一个具体类的构造函数,此时就需要使用类类型
*/
class TestE {
public info: string
constructor() {
console.log('constructor exec')
this.info = 'hhh'
}
}
// c的类型是对象类型且这个对象包含返回类型是T的构造函数。
// 理解c是一个对象(构造函数),执行new之后返回一个类型为T的对象
const testFn1 = <T>(c: new () => T): T => {
return new c()
}
testFn1(TestE)
const f = 400
enum Code {
OK,
Fault = f, // 可以用const或者函数返回值来编号
Error = 3, // 紧接着的一个枚举值要手动编号
Success,
}
console.log(Code[400]) // 可以通过编号获取枚举值
// 字符串枚举
enum reply {
Error = 'Sorry, an error has occurred',
Success = 'No problem',
Failed = Error, // 可以引用枚举
}
// 异构枚举, 枚举值类型不同(不推荐)
enum XX {
a = 1,
b = 'hehe',
}
如果一个枚举里所有成员的值都是字面量类型的值,那么这个枚举的每个成员和枚举本身都可以作为类型来使用。字面量枚举成员需满足以下条件:
-
符号的数字字面量,例如 enum E { A = 1 },enum E { A = -1 }// 枚举成员类型
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle // 使用 ShapeKind.Circle 作为类型,指定接口须有 kind 字段,且类型为 ShapeKind.Circle
radius: number
}
let c: Circle = {
kind: ShapeKind.Square, // Error! 因为接口 Circle 的 kind 被指定为 ShapeKind.Circle类型,所以这里会报错
radius: 100,
}
// 联合枚举
enum Status {
on,
off,
}
interface Light {
status: Status // 只能是Status中的枚举值
}
const light: Light = {
status: Status.off,
}
普通的枚举在编译后会实实在在创建一个对象(枚举名同名),然后让变量指向对象的值(枚举值),使用 const 枚举后不会创建这个对象,而是直接用
// const enum
const enum Animal {
Dog,
Cat,
GoldFish,
}
const kitty = Animal.Cat // 编译后是kitty=1, 不会产生Animal对象
// 断言为number
let a = 1
// 断言为number[]
let b = [1, 2]
// 类型兼容
let fn1 = (x: string, y: string): void => {
console.log(11)
}
let fn12 = (x: string): void => {
console.log(22)
}
// fn12 = fn1 // 函数,只能参数多的兼容少的
fn1 = fn12
interface Aitf {
name: string
}
interface Bitf {
name: string
age: number
}
let aObj: Aitf = { name: 'Samuel' }
let bObj: Bitf = { name: 'Samuel', age: 25 }
// bObj = aObj // 结构数据,只能成员少的兼容多的
aObj = bObj
// 重载类型兼容
function shk(x: number, y: number): number
function shk(x: string, y: string): string
function shk(x: any, y: any): any {
return x + y
}
function shl(x: number, y: number): number
function shl(x: any, y: any): any {
return x + y
}
let fna = shl
fna = shk // 重载类型少的兼容多的
// 类兼容性
class AC {
// public static num = 3.14 // 静态成员不参与兼容性检测
protected num: number
private age: number
constructor(public name: string, age: number) {
this.num = 13
this.age = age
}
}
class ACsub extends AC {
constructor(public name: string, age: number) {
super(name, age)
this.num = 15
}
}
class BC {
constructor(public name: string) {}
}
let ax: BC = new BC('name') // 结构成员
ax = new AC('hh', 17) // 少的兼容多的
ax = new ACsub('hh', 25) // protected以及private成员必须保证来自同一个类
console.log(ax)
交叉类型是将多个类型合并为一个类型。 可以理解成逻辑与
const mergeObj = <T, U>(a: T, b: U): T & U => {
let res = {} as T & U // 类型断言
res = Object.assign(a, b)
return res
}
可以理解成或
const showRes = (): string | number => {
const arr = ['Samuel', 123]
return Math.random() > 0.5 ? arr[0] : arr[1]
}
注意一点,举个例子:
interface Bird {
fly(): void
layEggs(): void
}
interface Turtle {
swim(): void
layEggs(): void
}
function getSmallPet(): Bird | Turtle {
return {} as Bird | Turtle
}
getSmallPet().layEggs() // 返回的联合类型只能访问其类型的共有属性
// getSmallPet().swim() // 各自的非共有属性无法访问
如果我们想确定变量的类型,比如是不是 string 类型或者其他类型,以往我们需要做大量的类型断言,ts 提供类型保护机制,可以方便使用
function isString(x: string | number): x is string {
return typeof x === 'string'
}
const res1 = showRes() // 联合类型 string | number
if (isString(res1)) {
console.log('string length:', res1.length)
} else {
console.log('number:', res1.toFixed())
}
typeof 做简单的类型保护
typeof 能判断的类型有:string/number/boolean/symbol/undefined/function,其他都是 object
ts 中用 typeof 做类型保护只能判断 string/number/boolean/symbol,其他类型不会识别为类型保护
// 对于简单的类型保护,可以直接使用typeof(只能做===或!==判断)
if (typeof res1 === 'string') {
console.log('string length:', res1.length)
} else {
console.log('number:', res1.toFixed())
}
intanceof 做类型保护
判断是否是特定类创建的对象
class Axx {
public age = 13
}
class Bxx {
public name = 'Sam'
}
function getRandomAB() {
return Math.random() > 0.5 ? new Axx() : new Bxx()
}
const rsAB = getRandomAB()
if (rsAB instanceof Axx) {
console.log(rsAB.age)
} else {
console.log(rsAB.name)
}
类型保护之 in
interface User {
name: string
age: number
occupation: string
}
interface Admin {
name: string
age: number
role: string
}
export type Person = User | Admin
export const persons: Person[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep',
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator',
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut',
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver',
},
]
export function logPerson(person: Person) {
let additionalInformation: string
// 通过in即可完成类型保护
if ('role' in person) {
additionalInformation = person.role
} else {
additionalInformation = person.occupation
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`)
}
persons.forEach(logPerson)
function getPrefixStr(x: number | null) {
function addPrefix(prefix: string): string {
// 因为下面已经对null情况进行排除,这里使用'!'类型断言x不为null/undefined
return prefix + x!.toFixed().toString()
}
x = x || 0.1
return addPrefix('pr')
}
类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
type Container<T> = { value: T }
type Tree<T> = {
value: T
left: Tree<T>
right: Tree<T>
}
type LinkedList<T> = T & { next: LinkedList<T> }
interface Person {
name: string
}
var people: LinkedList<Person>
var s = people.name
var s = people.next.name
var s = people.next.next.name
var s = people.next.next.next.name
type 可以给抽象类取别名,但是注意别名不能使用 extends,implement
abstract class AbsC {
protected name: string
protected constructor() {
this.name = 'Samuel'
}
}
type absc = AbsC // 不能Extends
class CCC extends AbsC {
constructor() {
super()
}
}
// 字符串字面量类型
type Direction = 'east' | 'west' | 'south' | 'north'
const DirFn = (direc: Direction) => {
console.log(direc)
}
DirFn('west')
// DirFn('hollyshit') // Argument of type '"hollyshit"' is not assignable to parameter of type 'Direction'.
// 同理还有数字字面量类型
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
type Shape = Square | Rectangle | Circle // 声明为联合类型
function area(s: Shape) {
// 类型保护
switch (s.kind) {
case 'square':
return s.size * s.size
case 'rectangle':
return s.height * s.width
case 'circle':
return Math.PI * s.radius ** 2
}
}
keyof
返回索引属性名构成的联合类型
interface ACtp2 {
name: string
age: number
}
let k: keyof ACtp2 // 返回索引属性名构成的联合类型 "name" | "age"
索引访问操作符
// --------------------------------------------------- T[K]是索引访问操作符
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map((n) => o[n])
}
interface Person {
name: string
age: number
}
let person: Person = {
name: 'Jarid',
age: 35,
}
let strings: string[] = pluck(person, ['name']) // ok, string[]
在普通的上下文里使用T[K]
需要保证K extends keyof T
使用[]
意味着取出属性(值或类型)
// [] 取出接口类型
interface Types {
a: number
b: string
c: boolean
}
type AllTypes = Types[keyof Types] // number | string | boolean
简单例子:将一个类型的所有属性都变成可读
type ReadOnlyTypes<T> = {
readonly [P in keyof T]: T[P] // in 表示遍历
}
内置的映射类型:
Readonly 只读
Partial 可选
Pick<T,K> T 里面保留 K(可以是联合类型)属性
Record<K extends keyof any, T> 创建一个类型,K 代表键值的类型, T 代表值的类型
// Record实例 将属性值映射为属性string的长度
function record<K extends string | number, T, U>(
obj: Record<K, T>,
fn: (x: T) => U
): Record<K, U> {
const res: any = {}
for (const key in obj) {
res[key] = fn(obj[key])
}
return res
}
console.log(record(person2, (p) => p.toString().length))
Omit<T,K> 实现排除已选的属性
type User = {
id: string
name: string
email: string
}
type UserWithoutEmail = Omit<User, 'email'> // alias for '{id:string,name:string}'
Exclude<T,U>
从 T 中提出可以赋值给 U 的类型
Extract<T, U>
-- 提取T
中可以赋值给U
的类型。
NonNullable<T>
-- 从T
中剔除null
和undefined
。
ReturnType<T>
-- 获取函数返回值类型。
InstanceType<T>
-- 获取构造函数类型的实例类型。
可以显式的指定添加或着删除修饰符
// 显示添加/删除修饰符
type RemoveReadonly<K> = {
-readonly [P in keyof K]: K[P] // - 表示删除修饰符,可以是readonly,?(可选修饰符)
}
type noReadOnlyTypes = RemoveReadonly<readTypess>
任何类型都可以赋值给 unknown 类型
在没有类型断言或者基于控制流的类型细化时,unknown 不可以赋值给其他类型
在没有类型断言或者基于控制流的类型细化时,不可以对 unknown 类型对象进行任何操作
let val2: unknown
// val2++ // Object is of type 'unknown'.
unknown 与其他类型组成交叉类型, 就等于其他类型
unknown 与其他类型组成联合类型, 等于 unknown
never 是 unknown 的子类型
keyof unknown 得到 never
unknown 类型只能进行===或!==操作, 不能进行其他操作
unknown 类型不能访问属性,作为函数调用以及作为类创建对象
使用类型映射时如果映射的属性是 unknown 类型,就不会产生映射
type NumsType<T> = {
[P in keyof T]: number
}
type xxd = NumsType<any> // Initial type:{[p: string]: number}
type xxs = NumsType<unknown> // Initial type:{}
extends 条件类型
type Typs<T> = T extends string ? string : number
let vals2: Typs<'2'> // let vals2: string
let vals3: Typs<false> // let vals3: number 走后面的逻辑
分布式条件类型
type onlyString<T> = T extends string ? T : never
type someTypes = 'a' | 'b' | 123 | false
type resultTypes = onlyString<someTypes> // Initial type: "a" | "b"
条件类型加索引类型
type reserveFunction<T> = {
[P in keyof T]: T[P] extends () => void ? P : never // 取出所有是函数类型的属性P, 其他为never
}[keyof T] // 索引属性来去掉never类型属性
/*
不加这个索引属性,下面的Res2会变成:
type Res2 = {
user: never;
code: never;
subparts: never;
updateTime: "updateTime";
thankU: "thankU"; }
*/
interface Part {
user: 'Sam'
code: 132
subparts: Part[]
updateTime: () => void
thankU(): void
}
type Res2 = reserveFunction<Part>
const obj2: Res2 = 'updateTime'
内置条件类型
Exclude<T,U> 从 T 里面排除 U
type typesA = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // "c"
Extract<T,U> 从 T 中选取可以赋值给 U 的类型
type typesB = Extract<'a' | 'b' | 'c', 'a' | 'b'> // "a" | "b"
NonNullable<T> 去掉 null 类型(undefined,null)
type typesC = NonNullable<'a' | 'b' | 'c' | null | undefined> // Initial type:"a" | "b" | "c"
ReturnType<T> T 的返回值类型
type typesD = ReturnType<() => string> //Initial type:string
InstanceType<T> ts 中类有 2 种类型, 静态部分的类型和实例的类型, 所以 T 如果是构造函数类型, 那么 InstanceType 可以返回他的实例类型
class AClass {
constructor() {
console.log('AClass')
}
}
interface AConstructor {
new (): string
}
type typeE = InstanceType<typeof AClass> // AClass
type typeF = InstanceType<AConstructor> // string
Parameters<T> 获取函数参数类型
interface A {
(a: number, b: string): string[]
}
type A1 = Parameters<A> // [number, string]
ConstructorParameters 获取构造函数的参数类型
class AClass {
constructor(public name: string) {
console.log('AClass')
}
}
type typeG = ConstructorParameters<typeof AClass> // string
// 推断元素类型
type Type2<T> = T extends any[] ? T[number] : T // 如果T是一个数组, 那就取T元素的类型(当元素类型不一样时是联合类型)
type typesX = Type2<string[]> // Initial type:string
type typesX2 = Type2<boolean[]> // Initial type:boolean
// infer 推断元素类型
type Type3<T> = T extends Array<infer U> ? U : T
type typesY = Type3<string[]> // Initial type:string
type typesY2 = Type3<boolean[]> // Initial type:boolean
type typesY3 = Type3<[false, 123, 'sam']> // Initial type:false | 123 | "sam"
// T是一个函数类型(参数:any)返回值any, infer P推断参数类型
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never
// T是一个构造函数类型
type ConstructorParameters<T extends new (...arg: any) => any> = T extends new (
...arg: infer P
) => any
? P
: never
为了防止全局变量过多,使用命名空间来管理
// components.ts
namespace Components {
// 导出需要暴露的内容
export class Header {
constructor() {
const elem = document.createElment('div')
elem.innerText = 'this is header'
document.body.appendChild(elem)
}
}
export class Content {}
export class Footer {}
// 可以暴露接口
export interface User {
name: string
}
// 可以暴露命名空间
export namespace Sub {
export class Test {}
}
}
///<reference path='./components.ts'>
/*
有一个组件命名空间,还有页面的命名空间
page.ts
*/
namespace Home {
export class Page {
constructor() {
new Components.Header()
new Components.Content()
new Components.Footer()
}
}
}
这个不太好用,好像没有挺多应用。
在给第三方库的描述文件中有很多引用。
针对全局的描述文件
// jquery.d.ts
// 声明全局变量
declare var $: (param: () => void) => void
// 定义接口
interface JqueryInstance {
html: (html: string) => JqueryInstance
}
// 声明全局函数
declare function $(readyFunc: () => void): void
declare function $(selector: string): JqueryInstance
// 声明一个对象(有属性嵌套, new $.fn.init())
declare namespace $ {
// 用namespace来
namespace fn {
class init {}
}
}
针对模块的描述文件
// jquery.d.ts
// 声明一个模块
declare module 'jquery' {
interface JqueryInstance {
html: (html: string) => JqueryInstance
}
declare function $(readyFunc: () => void): void
declare function $(selector: string): JqueryInstance
// 最终要导出出去
export = $
}
装饰器本身就是一个函数
类装饰器接收的参数是构造函数
function testDecorator(constructor: new (...args: any[]) => any) {
constructor.prototype.getName = () => {
console.log('dell')
}
console.log('decorator')
}
function testDecorator1(constructor: any) {
console.log('decorator1')
}
@testDecorator1
@testDecorator
class Test {}
;(new Test() as any).getName()
装饰器如果要支持接收参数,那么可以用闭包做装饰器
// 闭包作装饰器
function decor1(flag: boolean) {
if (flag) {
return function (constructor: any) {
// 修改构造函数的原型(类的原型)
constructor.prototype.info = () => {
console.log('dell')
}
}
}
return function (constructor: any) {}
}
@decor1(true) // 接收参数
class Test1 {}
// 但是这么写,ts无法检测到原型上的info函数,因此无法推断出Test1类型的对象上面有info函数
如果类装饰器返回一个值,他会使用提供的构造函数来替换类的声明。
// 类装饰器泛型
function testDecor<T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
name = 'lee'
getName() {
console.log(this.name)
}
}
}
@testDecor
class Person {
name: string
constructor(name: string) {
this.name = name
}
}
;(new Person('sam') as any).getName() // 这种写法TS识别不到getName(), 改进方式是以下写法
装饰器工厂函数写法
// 装饰器工厂函数写法
function decoratorFactory() {
return function <T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
name = 'lee'
getName() {
console.log(this.name)
}
}
}
}
// 利用工厂函数对匿名类进行装饰
const Person1 = decoratorFactory()(
class {
name: string
constructor(name: string) {
this.name = name
}
}
)
new Person1('sam').getName() // 拓展的方法可以显示出来
// 方法装饰器
function funcDecor(target: any, key: string, descriptor: PropertyDescriptor) {
/* target参数是方法对应的那个类的prototype
如果方法是静态方法,target是那个类的构造函数
*/
console.log(target)
console.log(key) // key是被装饰的方法名
console.log(descriptor) // js的属性描述
// descriptor.writable = false
// 直接修改方法
const oldVal = descriptor.value as (...args: any[]) => any
descriptor.value = function (...args: any[]) {
return oldVal.apply(this, args) + 'NO!!!!!!!!!!!'
}
}
class UObj {
name: string
constructor(name: string) {
this.name = name
}
// 方法装饰器
@funcDecor
getName() {
return this.name
}
}
const objU = new UObj('sam')
console.log(objU.getName())
注意: set 和 get 不能同时绑定,目前用处不知道。
function visitDecor(target: any, key: string, descriptor: PropertyDescriptor) {
console.log('descriptor:', descriptor)
}
class XObj {
private _name: string
constructor(name: string) {
this._name = name
}
@visitDecor
get name() {
return this._name
}
// @visitDecor
set name(name: string) {
this._name = name
}
}
访问器装饰器和方法装饰器区别:
属性装饰器不接受 descriptor 参数,但是可以返回一个新的 descriptor 来覆盖
// 属性装饰器
function propDecorator(target: any, key: string): any {
// 创建新的属性描述符来覆盖(返回)
/* const descriptor:PropertyDescriptor = {
writable: false
}
return descriptor */
// 因为target是类对应原型,因此修改是原型上的属性,而不是实例上的属性
target[key] = 'NoNoNO'
}
class XaObj {
@propDecorator
name = 'Dell'
}
const xao = new XaObj()
xao.name = 'Samuel'
console.log(xao.name) // Samuel
console.log((xao as any).__proto__.name) // NoNoNO
/**
*
* @param target 原型
* @param method 方法名
* @param paramIndex 参数的位置索引
*/
function paramDecorator(target: any, method: string, paramIndex: number) {
console.log(target, method, paramIndex)
}
class Sth {
getInfo(name: string, @paramDecorator age: number) {
console.log(name, age)
}
}
new Sth().getInfo('Sam', 30)
属性》方法》方法参数》类
元数据可以理解为在任意对象上挂载了一个 Map,用于绑定键值信息
import 'reflect-metadata'
function Role(name: string): ClassDecorator {
// 类装饰器, target是构造函数
return (target) => {
Reflect.defineMetadata('role', name, target.prototype)
console.log(target)
}
}
@Role('admin')
class Post {}
const metaData = Reflect.getMetadata('role', Post.prototype)
console.log(metaData)
const np = new Post()
const anotherMd = Reflect.getMetadata('role', (np as any).__proto__)
console.log(anotherMd)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。