TypeScript
阅读数:142 评论数:0
跳转到新版页面分类
html/css/js
正文
一、概述
TypeScript是微软开发的javascript加强版,就有带了type的javascript,主要用于解决:弱类型和没有命名空间,导致很难模块化。
TS允许你以接口的形式定义复杂的类型,当你要在应用程序中使用复杂的对象或数组时,会进行严格的静态类型审查,增加健壮性。
:<TypeAnnotation>
TypeScript的基本类型语法是在变量之后使用冒号进行类型标识,这种语法也揭示了TypeScript的类型声明实际上是可选的。
umi中内置了TypeScript的loader,可以直接创建.tsx或者.tx文件来写TypeScrpit代码。
1、首先安装依赖包
cnpm install tslint tslint-config-prettier tslint-react @types/react @types/react-dom --save
2、然后需要新建tsconfig.json和tslint.json文件
tsconfig.json来声明这是一个TypeScrpit项目,并进行配置。
tslint类似eslint是一个代码风格检查器。
二、tsconfig.json
你可以通过compilerOptions来定制你的编译选项:
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}
可以显式指定需要编译的文件:
{
"files": [
"./some/file.ts"
]
}
或者, 可以使用include和exclude选项来指定需要包含的文件, 和排除的文件:
{
"include": [
"./folder"
],
"exclude": [
"./folder/**/*.spec.ts",
"./folder/someSubFolder"
]
}
三. TypeScript类型
JavaScript原始类型也同样适应于TypeScript的类型系统, 因此string, number, boolean也可以被用作类型注解.
let bool: boolean = false;
let num: number = 10;
let str: string = 'sip';
类型+[]
数组的项中不允许出现其他的类型:
let fibonacci: number[] = [1, '1', 2, 3, 5];
// Type 'string' is not assignable to type 'number'.
(1)数组泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
(2)用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
它能合并多类型声明至一个类型声明
// 接口 Person (接口一般首字母大写)
interface Person {
name: string;
age: number;
}
let man: Person = {
name: 'Tom',
age: 25
}
(1)可选属性
可选属性的含义是该属性可以不存在,但不允许添加未定义的属性
interface Person {
name: string;
age?: number;
}
let man: Person = {
name: 'Tom'
};
let man2: Person = {
name: 'Tom',
age: 25
};
let man3: Person = {
name: 'Tom',
tel: '1370000000'
};
// error TS2322: Type '{ name: string; tel: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'tel' does not exist in type 'Person'.
(2)任意属性
一个接口中只能定义一个任意属性。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let man: Person = {
name: 'Tom',
gender: 'male'
};
使用 [propName: string]
定义了任意属性取 string
类型的值。
(3)只读属性
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let man: Person = {
id: 99999,
name: 'Tom',
gender: 'male'
};
man.id = 10000;
// error TS2540: Cannot assign to 'id' because it is a read-only property.
:{/*Structure*/}
any | 它提供一个类型系统的后门, TypeScript将会把类型检查关闭. |
null和undefined | 能被赋予给任意类型的变量 |
void | 表示一个函数没有返回值 |
let notSure: any = 'any';
notSure = 1;
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let something;
// 等同于 let something: any;
function alertName(): void {
alert('My name is Tom');
}
声明一个 void
类型的变量没有什么用,因为你只能将它赋值为 undefined
和 null
。
与 void
的区别是,undefined
和 null
是所有类型的子类型。也就是说 undefined
类型的变量,可以赋值给 number
类型的变量
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;
interface Array<T> {
reverse(): T[];
}
联合类型(Union Types)表示取值可以为多种类型中的一种。
let strNum: string | number;
strNum = 'seven';
strNum = 7;
enum Color {Red, Green, Blue} // 默认从0开始编号
let color: Color = Color.Green;
四、函数的类型
function sum(x: number, y: number): number {
return x + y;
}
// 注意,输入多余的(或者少于要求的)参数,是不被允许的:
sum(1, 2, 3);
// error TS2554: Expected 2 arguments, but got 3.
sum(1);
// error TS2554: Expected 2 arguments, but got 1.
let mySum = function (x: number, y: number): number {
return x + y;
};
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
与接口中的可选属性类似,我们用 ?
表示可选的参数,需要注意的是,可选参数必须接在必需参数后面。
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
五、组合式api
当使用 <script setup>
时,defineProps()宏函数支持从它的参数中推导类型
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
</script>
然而,声明式更直接
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
另外,我们还可以将类型移入一个单独的接口中
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])
// 基于类型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
ref 会根据初始化时的值推导其类型:
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
想为 ref 内的值指定一个更复杂的类型,可以通过使用 Ref
这个类型
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // 成功!
或者,在调用 ref()
时传入一个泛型参数,来覆盖默认的推导行为:
// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
reactive()
也会隐式地从它的参数中推导类型
import { reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })
要显式地标注一个 reactive
变量的类型,我们可以使用接口:
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 指引' })
computed()
会自动从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'
const count = ref(0)
// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
还可以通过泛型参数显式指定类型:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
<script setup lang="ts">
function handleChange(event) {
// `event` 隐式地标注为 `any` 类型
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
没有类型标注时,这个 event
参数会隐式地标注为 any
类型。这也会在 tsconfig.json
中配置了 "strict": true
或 "noImplicitAny": true
时报出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你可能需要显式地强制转换 event
上的属性:
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey
接口,它是一个继承自 Symbol
的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // 若提供的是非字符串值会导致错误
const foo = inject(key) // foo 的类型:string | undefined
模板引用需要通过一个显式指定的泛型参数和一个初始值 null
来创建:
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
el.value?.focus()
})
</script>
<template>
<input ref="el" />
</template>
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
为了获取 MyModal
的类型,我们首先需要通过 typeof
得到其类型,再使用 TypeScript 内置的 InstanceType
工具类型来获取其实例类型:
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>