TypeScript 基础入门
类型定义
基础类型请用小写定义类型 例如 string number,因为 String Number 是 JS 对象
ts 具有自动类型推断 大部分时候不需要定义类型,或者显式的定义变量的类型注释
string
let str1 = 'string'
const str2: string = 'string'
str1 = 1 // Type 'number' is not assignable to type 'string'.
number
let num1 = 3.14
const num2: number = 3.14
num1 = false // Type 'boolean' is not assignable to type 'number'.
boolean
let bool1 = true
const bool2: boolean = true
bool1 = null // Type 'null' is not assignable to type 'boolean'.
null
let null1 = null // 如果赋值 null 或者 undefined 且没有定义类型 ts 会推断成 any 类型
const null2: null = null
null1 = 2 // any 可以赋值任何类型
null1 = '' // any 可以赋值任何类型
undefined
let void1 = undefined // 如果赋值 null 或者 undefined 且没有定义类型 ts 会推断成 any 类型
const void2: undefined = undefined
void1 = 2 // any 可以赋值任何类型
void1 = '' // any 可以赋值任何类型
symbol
let sym1 = Symbol.iterator
const sym2: symbol = Symbol('sym2')
sym1 = 1 // Type 'number' is not assignable to type 'symbol'.
bigint
let bigint1 = 1n
const bigint2: bigint = BigInt(9007199254740991)
bigint1 = 1 // Type 'number' is not assignable to type 'bigint'.
any
// 类似 dart 中的 dynamic 类型
// any 必须要定义类型,不然 ts 会尝试推断成其他类型
// 可以被赋值为任意类型 使用时 ts 不会强制对类型进行校验
let any1: any = {a: 1, b: '2'}
any1 = 1 // 如果上面没有定义 any 类型,会提示 Type 'number' is not assignable to type '{ a: number; b: string; }'.
any1 = true // 如果上面没有定义 any 类型,会提示 Type 'boolean' is not assignable to type '{ a: number; b: string; }'
unknown
// 类似 dart 中的 Object 类型
// 可以被赋值为任意类型 使用时 ts 会强制对类型进行校验
function invokeUnknownCallback(callback: unknown): void {
// callback() // 'callback' is of type 'unknown'.
if (typeof callback === 'function') callback()
}
function invokeAnyCallback(callback: any): void {
callback()
}
array
// 除了基础类型,其他类型定义数组时推荐添加上泛型
const arr1: any[] = [1, '2', true]
const arr2: string[] = ['1', '2']
const arr3: Array<string> = ['1', '2']
const arr4 = [1, 2] // 会自动推断类型为 number[]
arr1.push(null) // success
arr2.push(3) // Argument of type 'number' is not assignable to parameter of type 'string'.
arr3.push(3) // Argument of type 'number' is not assignable to parameter of type 'string'.
tuple
返回固定长度和类型的数组 React.useState
// 以下是很经典的元组类型使用场景 [React.useState](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L941C5-L941C97) 类型定义的一种
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>]
const [getVal, setVal] = useState<string>('value')
// useState[0] 表示 get
// useState[1] 表示 set
function
const fn1 = (age: number): number => age * 2
function fn2(sex: 0 | 1): string {
return ['女', '男'][sex]
}
void
表示不返回值的函数 的返回值
function fn1(): void {
// do something
// 没有返回值
}
function fn2(): number {
// do something
// 有返回值
return 1
}
never
永远都不会有返回值
// 下面的函数抛出了异常,永远不会有返回值
function neverReturns(): never {
throw new TypeError('never 类型永远不会返回值')
}
// 死循环 也不会有返回值
while (true) {}
自定义类型
enum
枚举
enum SexType {
Female = 0,
Male // 自动推断类型1
}
// SexType 编译后输出
var SexType = /*#__PURE__*/function (SexType) {
SexType[SexType["Female"] = 0] = "Female";
SexType[SexType["Male"] = 1] = "Male";
return SexType;
}(SexType || {});
// 如果枚举需要 toString 请在就近定义
function SexTypeToString(sex: SexType): string {
switch (sex) {
case SexType.Male:
return '男'
case SexType.Female:
return '女'
}
}
enum UserType {
Admin = 2, // 默认从0开始自增
Teacher, // 3
Developer = 'Developer'
}
// UserType 编译后输出
var UserType = /*#__PURE__*/function (UserType) {
UserType[UserType["Admin"] = 2] = "Admin";
UserType[UserType["Teacher"] = 3] = "Teacher";
UserType["Developer"] = "Developer";
return UserType;
}(UserType || {});
union type
type ButtonType = 'primary' | 'danger' | 'text' | 'link'
// [以下是 Element Plus 中 Button 的 type 类型定义](https://github.com/element-plus/element-plus/blob/dev/packages/components/button/src/button.ts#L6)
interface
接口
interface User {
avatar: string
id: number
name: string
}
const user: User = {
avatar: 'https://xx.com',
id: 0,
name: 'name'
}
// const user: User = {} // Type '{}' is missing the following properties from type 'User': avatar, id, name
// interface 多个类型合并 使用 extends 多个类型使用 ,区分
interface State {
avatar: string
}
interface Method {
setAvatar: (url: string) => void
}
interface Store extends State, Method {}
const store: Store = {
avatar: '',
setAvatar(url) {
this.avatar = url
},
}
class classStore implements Store {
avatar: string
constructor() {
this.avatar = ''
}
setAvatar(url: string): void {
this.avatar = url
}
}
// interface 也能定义函数类型
interface Fn {
(int: number): number
}
const fn: Fn = () => 1
type
类型别名
type User = {
avatar: string
id: number
name: string
}
// 定义类型别名
type Admin = User
// type 多个类型合并 使用 & 多个类型就多用几次 &
type Admin = User & {
phone: string
} & Method
// interface 也能定义函数类型
type Fn = (a: string) => void
const fn: Fn = (a) => void 0
type 和 interface 的区别
-
定义方式
-
类型别名使用
type关键字进行定义,例如:type MyType = number | string -
接口使用
interface关键字进行定义,例如:interface MyInterface { prop: number }
- 重复定义
type不能重复定义,如果多次定义同一个type,会报错。interface可以多次定义 ,并且会将多个interface自动合并为一个,例如:
interface User { name: string } interface User { age: number } const MyUser: User = { name: 'nicki', age: 41 } // type MyUser = { name: string, age: number }
-
-
可以扩展:
- 类型别名可以使用
&符号进行扩展,例如:type MyType = { prop1: number } & { prop2: string },此时MyType的类型为{ prop1: number; prop2: string }。 - 接口可以使用
extends关键字进行扩展,例如:interface MyInterface extends AnotherInterface { prop: number },此时MyInterface继承了AnotherInterface接口的属性和方法。
- 类型别名可以使用
-
可以实现:
type不能被类实现,因为type只是类型的别名,不会生成新的类型。interface可以被类实现,类可以通过实现interface来强制实现interface中定义的属性和方法。
总结
type更适合用于定义复杂的类型,而interface更适合用于定义对象的结构和行为。在实际使用中,可以根据具体的需求选择使用type或interface。
空值处理
ES 新增语法
type User = {
name: string
sex: 0 | 1
phone: string | null // 表示 phone 的 key 已经定义 但是 值可能是 string 或者 null
address?: string // 表示 address 这个 key 可能未定义, 假设和后端定义好的 如果 sex 等于 0 那么 address 肯定有值
}
const user: User = {
name: 'xx',
sex: 1,
phone: null
}
// 使用 ES2020 新增的可选链
{
// ?.
// ??
// ??=
// ||=
// &&=
user.phone?.toUpperCase()
type OptionalChaining = {
a: null | {
$a: string
b: null | {
c: null | (() => void)
}
c?: {
d: {
e?: number
}
}
}
}
const optional: OptionalChaining = {
a: null
}
// optional.a.b // 'optional.a' is possibly 'null'.
const $a = optional.a?.$a
optional?.a?.b?.c?.call(null)
let e = optional?.a?.c?.d.e
const hasE = (e ?? -1) !== -1
e ??= 1
}
// 使用非空断言 这是 ts 特有的
// ! 可以使前面的值肯定不为 null 或 undefined
{
// 例如 sex 等于 0 address 肯定有值
if (user.sex === 0) console.log(user.address!) // 这里的 address 类型为 string 而不是 string | undefined
}
泛型
给什么就返回什么类型 不用多次定义类型
常见泛型缩写
T 表示 Type
E 表示 Element
K 表示 Key
V 表示 Value
// 常见的泛型
// numberArr 的类容只能为 number 类型
const numberArr: Array<number> = [1, 2]
// 返回 Promise<string>
var asynchronous = () => new Promise<string>(resolve => resolve('success'))
// 返回 Promise<void>
async function asynchronous() {}
// 返回 Promise<number>
async function asynchronous() {
return 1
}
// canvas 可能为 null 找不到这个 dom 可以使用 ! 或者 其他非空判断
const canvas = document.querySelector<HTMLCanvasElement>('#root')
const isCanvas1 = canvas instanceof HTMLCanvasElement
const isCanvas2 = canvas !== null
// 如果上面的 document.querySelector 不加泛型参数的话 ts 就没法推荐是什么类型的 HTMLElement
// 就没法使用下面 HTMLCanvasElement 对象特定的方法 比如 HTMLCanvasElement.getContext
canvas!.getContext('2d')
// document.querySelector('canvas') 如果查询语句是简单的标签 ts 能自动推断类型为 HTMLCanvasElement | null
// 可能为空
type Nullable<T> = T | null
// http 返回数据
interface HttpResponse<T> {
msg: string
code: 200 | 400 | 500
data: Nullable<T> // 如果 code !== 200 的情况下 data 可能为 null
}
// 分页数据
type PaginationResponse<T> = {
count: number
page: number
limit: number
data: T[]
}
js 内置常用类型
Array<T>
const array: string[] = []
Promise<T>
const promise = () => new Promise<number>(resolve => resolve(2))
MouseEvent
const Button = () => {
const handleClick = (e: MouseEvent) => void 0
return <button onClick={handleClick}>click me</button>
}
HTMLElement
const video = document.querySelector<HTMLVideoElement>('video#player')!
ts 内置常用类型
type Type = {
id: number
sex: 0 | 1
avatar: string
address: string[]
phone: string | null
}
Pick
选择部分类型
type pick = Pick<Type, 'id'> // {id: number}
Omit
删除部分类型
type omit = Omit<Type, 'id'> // {sex: 0 | 1, avatar: string, address: string[], phone: string | null}
Partial
让类型可选
type partial = Partial<Type> // {id?: number, sex?: 0 | 1, avatar?: string, address?: string[], phone?: string | null}
Required
让类型必填
type required = Required<partial> // {id: number, sex: 0 | 1, avatar: string, address: string[], phone: string | null}
Readonly
让类型只读 不能修改
type myReadonly = Readonly<Type> // {readonly id: number, readonly sex: 0 | 1, readonly avatar: string, readonly address: string[], readonly phone: string | null}
const testReadonly: myReadonly = {
id: 0,
sex: 1,
avatar: '',
address: [],
phone: null
}
// change id
testReadonly.id = 2 // Cannot assign to 'id' because it is a read-only property.
Record
type record = Record<'id' | 'avatar', string> // {id: string, avatar: string}
Parameters
获取函数参数类型
function testType(arg1: number, arg2: string): number | void {}
type testParameters = Parameters<typeof testType> // [number, string]
ReturnType
获取函数返回值类型
function testType(arg1: number, arg2: string): number | void {}
type testReturnType = ReturnType<typeof testType> // number | void
类型断言
const nullable: number | null = null
var brackets = <number>nullable // number
var brackets = <string>nullable // string
var brackets = <bigint><string>nullable // bigint
var $as = nullable as number // number
var $as = nullable as string // string
var $as = nullable as string as bigint // bigint
const notEmpty = nullable! // number
tsconfig常见配置
/// tsconfig.json
{
"compilerOptions": {
// 指定类型声明文件的根文件夹路径。
// 当指定了 typeRoots 选项时,编译器会在指定的根文件夹中查找类型声明文件,并将其包含在编译中。
"typeRoots": [
"./node_modules/@types/",
"./types"
],
// 严格模式
"strict": true,
// jsx 编译选项
"jsx": "preserve",
"baseUrl": "./",
// paths 配置 ts 类型的 alias,vite 的需要单的配置
"paths": {
"@": [
"src"
],
"@/*": [
"src/*"
]
}
},
// 包含某些路径类型的编译
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"build/**/*.ts",
"build/**/*.d.ts",
"vite.config.ts"
],
// 忽略某些路径类型的编译
"exclude": [
"node_modules",
"dist",
"**/*.js"
]
}
配合 vue3 使用
base type
// 基础类型可以不使用泛型 如果默认值为 null 需要定义泛型
const name = ref<string>('nicki')
const goods = reactive<Record<'id' | 'price', string>>({id: '11', price: '20.2'})
const discountedPrice = computed<number>(() => Number.parseFloat(goods.price) * .6)
component props
interface BaseCosUploadProps {
max?: number,
accept?: string,
listType?: 'text' | 'picture' | 'picture-card',
showPercent: boolean,
isVideo: boolean,
multiple?: boolean,
value: BaseFileItem[],
showUploadList?: boolean | {
showRemoveIcon: (boolean | ((props: Record<string, unknown>) => boolean)) & boolean,
showPreviewIcon: (boolean | ((props: Record<string, unknown>) => boolean)) & boolean,
},
buttonProps?: ButtonProps,
customRequest?(files: BaseFileItem[]): void,
disabled?: boolean,
}
// 使用泛型定义 props 并通过编译器宏设置默认值
const props = withDefaults(defineProps<BaseCosUploadProps>(), {
accept: 'image/*',
listType: 'text',
showPercent: false,
isVideo: false,
multiple: false,
showUploadList: void 0,
buttonProps: void 0,
disabled: void 0,
})
emit
const emit = defineEmits<{
(e: 'success', data: COS.PutObjectResult[] | BaseUploadVideoResult, originData: BaseFileItem[]): void,
(e: 'delete', data: BaseFileItem[]): void,
(e: 'update:value', data: BaseFileItem[]): void,
(e: 'change', data: BaseFileItem[]): void,
(e: 'progress', data: COS.ProgressInfo): void,
}>()
provide and inject
type CourseSectionPendingUploadListValue = {
list: User[]
pending: User[]
}
provide('pendingUploadList', pendingUploadListValue)
const pendingUploadList = inject<CourseSectionPendingUploadListValue>('pendingUploadList', {})
event bus
推荐使用 mitt
import mitt from 'mitt'
type Events = {
open: undefined
showTextEditor: Konva.Text
hideTextEditor: undefined
$1: Tpl1JSON
$2: Tpl2JSON
$3: Tpl3JSON
$4: Tpl4JSON
$5: Tpl5JSON
}
const eventBus = mitt<Events>() // inferred as Emitter<Events>
eventBus.on('showTextEditor', (text) => void 0) // 'text' has inferred type 'Konva.Text'
eventBus.emit('showTextEditor', 'test') // Error: Argument of type 'string' is not assignable to parameter of type 'Konva.Text'. (2345)
http
const http = axios.create()
type User = {
avatar: string
id: number
name: string
}
function fetchUserInfo(id: number) {
return http.get<User>(`/user?ID={id}`)
}
function setUserInfo(info: Partial<Omit<User, 'id'>>) {
return http.post<null>('/user', info)
}
定义全局类型
localStorage 类型
type Nullable<T> = T | null
type Profile = {
name: string
avatar: string
id: number
address: Nullable<string>
sex: 0 | 1
}
type AppCache = {
token: string
profile: Profile
}
export function getCache<K extends keyof AppCache>(key: K): Nullable<AppCache[K]> {
const v = localStorage.getItem(key)
return v === null ? null : JSON.parse(v) as AppCache[K]
}
export function setCache<T extends AppCache[K], K extends keyof AppCache>(key: K, value: T): T {
localStorage.setItem(key, JSON.stringify(value))
return value
}
export function clearCache() {
return localStorage.clear()
}
const token = getCache('token') // Nullable<string>
const profile = getCache('profile') // Nullable<Profile>
setCache('token', true) // Argument of type 'boolean' is not assignable to parameter of type 'string'.
h5 和 app 通讯
type Methods = {
// get app version, like 1.2.3
getVersion: () => string
// get file base64 string
getFile: (path: string) => string | undefined
}
declare interface Window {
flutter_inappwebview?: {
callHandler: <K extends keyof Methods>(name: K, ...args: Parameters<Methods[K]>) => Promise<ReturnType<Methods[K]>>
}
}
window.addEventListener('flutterInAppWebViewPlatformReady', async function (e): Promise<void> {
const version = await window.flutter_inappwebview!.callHandler('getVersion')
console.log(version) // 0.1.2
const base64 = await window.flutter_inappwebview!.callHandler('getFile', '/cache/img1.jpg')
console.log(base64) // data:image/jpeg;base64,xxx | undefined
})
node 环境变量
declare namespace NodeJS {
interface ProcessEnv {
// 环境变量
NODE_ENV: 'development' | 'production'
// api请求地址
API_BASE_URL: string
}
}
const isDevMode = process.env.NODE_ENV === 'development'