TypeScript 是什么?
是以JavaScript为基础构建的语言,是一个JavaScript的超集,可以在任何支持JavaScript的平台中执行,但是不能被JS解析器直接执行。TS可以编译成JS,TypeScript扩展了JavaScript,并添加了类型
JavaScript是弱类型, 很多错误只有在运行时才会被发,而TypeScript提供了一套静态检测机制, 可以帮助我们在编译时就发现错误
//这里指定了 a 的类型,之后a只能是number类型
let a : number;
a = 1
a = 20;
//a = 'string'; error
let b : string;
b = 'hello';
let c : boolean = true;
c = false;
//c = 123; error
let d = false;
//d = 123; error d是boolean类型
//如果声明和赋值在同一行则会自动检测类型
let e;
e = 123;
e = false;
// symbol 类型
const sym = Symbol();
let obj = {
[sym]: "semlinker",
};
console.log(obj[sym]); // semlinker
//JS的函数参数不考虑类型和个数
function sum (a,b){
return a + b;
}
sum(123,456); //468
sum(123,"345"); //"123456"
//现在对参数的类型和个数就有要求了
function sumts(a : number,b : number) : number {
return a + b;
}
sumts(123,456);
//sumts(123,"456"); error
//sumts(123,456,789); error
let result = sumts(123,456); //result -> number
类型
let a : 10;
a = 10;
//a = 11; error
//可以使用 | 来连接多个类型(联合类型)
let b : "true" | "false";
b = "true";
b = "false";
//b = 1; error
let c : string | number;
c = "123";
c = 123;
//c = false; error
//一个变量使用any来声明相当于关闭了ts的类型检查
let d : any; //显式any
let f; //隐式any
d = false;
d = 123;
d = "false";
f = "123";
let s : string;
s = f;
s = d;
let u : unknown;
u = 1;
u = false;
u = "123";
//s = u; error 不能将类型“unknown”分配给类型“string”
//unknown 是一个类型安全的any
//unknoen类型的值不能直接赋值给别的变量 ,可以进行类型检查后赋值
if(typeof u === "string") {
s = u;
}
//类型断言
s = u as string;
s = <string> u;
//没有返回值
function noreturn() : void {
return;
}
//object 表示一个对象
let obj : object;
obj = {};
obj = function() {};
//{} 用来表示对象中包含哪些属性
//语法 {属性名:属性类型,属性名:属性类型}
let g : {name : string};
g = {name : "123"};
//g = {name : "123",age = 18};
//不能将类型“{ name: string; age: number; }”分配给类型“{ name: string; }”。
//对象字面量只能指定已知属性,并且“age”不在类型“{ name: string; }”中
// ? 表示可选
let gg : {name : string,age?: number};
gg = {name : "123"};
gg = {name : "123",age : 16};
/*
* 设置函数结构的类型声明
* 语法:(形参:类型,形参:类型 ... ) => 返回值类型
*/
let h : (a :number,b : number) => number;
//(local function)(a: number, b: number): number
h = function(a,b) : number {
return a + b;
}
//数组
// string[] Array<>
let arr : string[];
arr = ["12","2"];
let arra : number[];
let ar : Array<number>;
let arr:(number | string)[];
// 表示定义了一个名称叫做arr的数组,
// 这个数组中将来既可以存储数值类型的数据, 也可以存储字符串类型的数据
arr3 = [1, 'b', 2, 'c'];
// interface是接口
//定义指定对象成员的数组:
interface Arrobj{
name:string,
age:number
}
let arr3:Arrobj[]=[{name:'jimmy',age:22}]
//元组 元组是固定长度的数组
// 语法:[类型,类型]
let tup : [string,number];
tup = ['hello',123];
//元组最重要的特性是可以限制数组元素的个数和类型,
//它特别适合用来实现多值返回。
//元祖用于保存定长定数据类型的数据
// enum 枚举
enum Gender{
male = 0,
female = 1
}
let en : {name : string,gender : Gender};
en = {name : "123",gender : Gender.male};
console.log(en.gender === 1);
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0
//数字枚举除了支持 从成员名称到成员值 的普通映射之外,
//它还支持 从成员值到成员名称 的反向映射:
// & 表示同时
let j : {name : string} & {age : number};
j = {name : "123",age : 1};
//类型别名
type myType = 1 | 2 | 3 | 4 | 5;
let k : 1 | 2 | 3 | 4 | 5;
let l : myType;
k = 1;
k = 2;
//k = 6; error 不能将类型“6”分配给类型“2 | 1 | 3 | 4 | 5”
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null
和 undefined
赋值给其他类型。
// null和undefined赋值给string
let str:string = "666";
str = null
str= undefined
// null和undefined赋值给number
let num:number = 666;
num = null
num= undefined
// null和undefined赋值给object
let obj:object ={};
obj = null
obj= undefined
// null和undefined赋值给Symbol
let sym: symbol = Symbol("me");
sym = null
sym= undefined
// null和undefined赋值给boolean
let isDone: boolean = false;
isDone = null
isDone= undefined
// null和undefined赋值给bigint
let big: bigint = 100n;
big = null
big= undefined
配置文件
vscode 中终端输入 tsc --init 自动生成 tsconfig.json文件
然后输入 tsc 命令,直接编译全部文件
include 和 exclude
vscode自动生成的代码:
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
l
类的写法:接口抽象类
//类
class person{
name : string = "123";
age : number = 16;
static gender = 1;
sayHello(){
console.log("hello");
}
static sayBye(){
console.log("Bye");
}
constructor(name : string,age : number){
this.name = name;
this.age = age;
}
}
//静态与非静态
const per = new person("wa",18);
console.log(person.gender);
console.log(per.age);
per.sayHello();
person.sayBye();
//继承 用 extends
class male extends person{
sayHello(): void {
//父类中的方法
super.sayHello();
}
count : number;
//构造函数
constructor(name : string,age : number,count : number) {
super(name,age);
this.count = count;
}
}
//抽象类,不可以创建它的实例
abstract class abs{
//抽象方法
abstract sayHello() : void;
}
interface myIterface{
myname : string;
age : number;
}
//相当于是对上面接口的扩充,两个并做一个
//所以下面实现接口的inter需要写三个属性
interface myIterface{
gender : number;
}
//注意这个里面是写逗号
const inter : myIterface = {
myname : "1",
age : 11,
gender : 1
};
//只管定义,不能包含实现
interface myInter{
name : string;
sayHello() : void;
}
//实现这个接口
class MyClass implements myInter{
name : "1";
sayHello() : void {
}
}
属性封装
class puppy{
//public 默认值,任意处可修改
//protected 受保护的,当前类和当前类的子类中使用
//private 私有属性,只能在类内部修改
private _pname : string;
public _age : number;
constructor(name : string,age : number) {
this._pname = name;
this._age = age;
}
get pname(){
return this._pname;
}
set pname(value : string) {
this._pname = value;
}
}
console.log(puppy.name);
class mimi{
//语法糖:可以直接将属性定义在构造函数中
constructor(public name : string,public age : number) {
}
}
const mi = new mimi("xxx",123);
泛型
//泛型
function fn<T>(a : T) :T {
return a;
}
fn<number>(111);
fn(10);
//泛型约束 需要实现了myInter接口的类型
function fun2<T extends myInter>(a : T) : string{
return a.name;
}
class MyyClass<T>{
name : T;
constructor(name : T) {
this.name = name;
}
}
const mc = new MyyClass<string>("123");
函数
//函数声明
function sum(x: number, y: number): number {
return x + y;
}
//函数表达式
let mySum: (x: number, y: number) => number
= function (x: number, y: number): number {
return x + y;
};
//用接口定义函数类型
interface SearchFunc{
(source: string, subString: string): boolean;
}
参数
剩余参数
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
可选参数
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, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
默认参数没有位置要求
函数重载
type Types = number | string
function add(a:number,b:number):number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a:Types, b:Types) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add('Semlinker', ' Kakuqo');
result.split(' ');
//如果没有重载的话会报错,说 number(一个可能的结果)里面没有 split 函数,就算参数和结果都是string,可以用重载来解决这个问题
元组
let x: [string, number];
// 类型必须匹配且个数必须为2
x = ['hello', 10]; // OK
x = ['hello', 10,10]; // Error
x = [10, 'hello']; // Error
//我们可以通过下标的方式来访问元组中的元素,
//当元组中的元素较多时,这种方式并不是那么便捷。
//其实元组也是支持解构赋值的:
let employee: [number, string] = [1, "Semlinker"];
let [id, username] = employee;
console.log(`id: ${id}`);
console.log(`username: ${username}`);
//控制台输出:
//id: 1
//username: Semlinker
//可选元素
let optionalTuple: [string, boolean?];
optionalTuple = ["Semlinker", true];
console.log(`optionalTuple : ${optionalTuple}`);
optionalTuple = ["Kakuqo"];
console.log(`optionalTuple : ${optionalTuple}`);
void
void表示没有任何类型,和其他类型是平等关系,不能直接赋值:
let a: void;
let b: number = a; // Error
//函数没有返回值时声明成 void
function fun(): undefined {
console.log("this is TypeScript");
};
fun(); // Error 报错
never
//never类型表示的是那些永不存在的值的类型。
//如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值
//(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
//函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
// 异常
function err(msg: string): never { // OK
throw new Error(msg);
}
// 死循环
function loopForever(): never { // OK
while (true) {};
}
//never类型同null和undefined一样,也是任何类型的子类型,也可以赋值给任何类型。
//但是没有类型是never的子类型或可以赋值给never类型(除了never本身之外),
//即使any也不可以赋值给never
let ne: never;
let nev: never;
let an: any;
ne = 123; // Error
ne = nev; // OK
ne = an; // Error
ne = (() => { throw new Error("异常"); })(); // OK
ne = (() => { while(true) {} })(); // OK
unknow
//unknown与any一样,所有类型都可以分配给unknown:
let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK
//unknown与any的最大区别是: 任何类型的值可以赋值给any,
//同时any类型的值也可以赋值给任何类型。
//unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any
//如果不缩小类型,就无法对unknown类型执行任何操作:
function getDog() {
return '123'
}
const dog: unknown = {hello: getDog};
dog.hello(); // Error
//这种机制起到了很强的预防性,更安全,这就要求我们必须缩小类型,
//我们可以使用typeof、类型断言等方式来缩小未知范围:
function getDogName() {
let x: unknown;
return x;
};
const dogName = getDogName();
// 直接使用
const upName = dogName.toLowerCase(); // Error
// typeof
if (typeof dogName === 'string') {
const upName = dogName.toLowerCase(); // OK
}
// 类型断言
const upName = (dogName as string).toLowerCase(); // OK
Number、String、Boolean、Symbol
很容易和原始类型 number、string、boolean、symbol 混淆的首字母大写的 Number、String、Boolean、Symbol 类型,后者是相应原始类型的包装对象
,姑且把它们称之为对象类型。
从类型兼容性上看,原始类型不兼容对应的对象类型,反过来对象类型兼容对应的原始类型。
let num: number;
let Num: Number;
Num = num; // ok
num = Num; // ts(2322)报错
object、Object 和 {}
小 object 代表的是所有非原始类型,也就是说我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null
和 undefined
类型也不能赋给 object。
JavaScript 中以下类型被视为原始类型:
string
、boolean
、number
、bigint
、symbol
、null
和undefined
。
let lowerCaseObject: object;
lowerCaseObject = 1; // ts(2322)
lowerCaseObject = 'a'; // ts(2322)
lowerCaseObject = true; // ts(2322)
lowerCaseObject = null; // ts(2322)
lowerCaseObject = undefined; // ts(2322)
lowerCaseObject = {}; // ok
大Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object。
let upperCaseObject: Object;
upperCaseObject = 1; // ok
upperCaseObject = 'a'; // ok
upperCaseObject = true; // ok
upperCaseObject = null; // ts(2322)
upperCaseObject = undefined; // ts(2322)
upperCaseObject = {}; // ok
{}、大 Object 是比小 object 更宽泛的类型(least specific),{} 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。
类型推断
//在 TypeScript 中,具有初始化值的变量、有默认值的函数参数、
//函数返回的类型都可以根据上下文推断出来。
//比如我们能根据 return 语句推断函数返回的类型,如下代码所示:
{
/** 根据参数的类型,推断出返回值的类型也是 number */
function add1(a: number, b: number) {
return a + b;
}
const x1= add1(1, 1); // 推断出 x1 的类型也是 number
/** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
function add2(a: number, b = 1) {
return a + b;
}
const x2 = add2(1);
const x3 = add2(1, '1'); // ts(2345) Argument of type "1" is not assignable to parameter of type 'number | undefined
}
类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 提示 ts(2322)
//其中,greaterThan2 一定是一个数字(确切地讲是 3),因为 arrayNumber 中明显有大于 2 的成员,
//但静态类型对运行时的逻辑无能为力。
//在 TypeScript 看来,greaterThan2 的类型既可能是数字,
//也可能是 undefined,所以上面的示例中提示了一个 ts(2322) 错误,
//此时我们不能把类型 undefined 分配给类型 number。
//我们可以用 as 来做类型断言
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
//语法
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
非空断言
//在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 !
//可以用于断言操作对象是非 null 和非 undefined 类型。
//具体而言,x! 将从 x 值域中排除 null 和 undefined 。
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}
确定赋值断言
允许在实例属性和变量声明后面放置一个 !
号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
//可以使用确定赋值断言:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
//通过 let x!: number; 确定赋值断言,
//TypeScript 编译器就会知道该属性会被明确地赋值。
字面量类型
在TypeScript中,字面量不仅可以表示值,还可以表示类型,即字面量类型
//目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、
//数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、
//布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:
{
let specifiedStr: 'this is string' = 'this is string';
let specifiedNum: 1 = 1;
let specifiedBoolean: true = true;
}
//比如 'this is string' (这里表示一个字符串字面量类型)类型是 string 类型
//(确切地说是 string 类型的子类型),而 string 类型不一定是 'this is string'
//(这里表示一个字符串字面量类型)类型,如下具体示例:
{
let specifiedStr: 'this is string' = 'this is string';
let str: string = 'any string';
specifiedStr = str; // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
str = specifiedStr; // ok
}
字符串字面量类型
可以把多个字面量类型组合成一个联合类型(后面会讲解),用来描述拥有明确成员的实用的集合。
type Direction = 'up' | 'down';
function move(dir: Direction) {
// ...
}
move('up'); // ok
move('right'); // ts(2345) Argument of type '"right"' is not assignable to parameter of type 'Direction'
//通过使用字面量类型组合的联合类型,我们可以限制函数的参数为指定的字面量类型集合,
//然后编译器会检查参数是否是指定的字面量类型集合里的成员。
数字字面量类型以及布尔字面量类型
我们可以使用字面量组合的联合类型将函数的参数限定为更具体的类型
interface Config {
size: 'small' | 'big';
isEnable: true | false;
margin: 0 | 2 | 4;
}
let和const分析
{
const str = 'this is string'; // str: 'this is string'
const num = 1; // num: 1
const bool = true; // bool: true
}
//我们将 const 定义为一个不可变更的常量,在缺省类型注解的情况下,
//TypeScript 推断出它的类型直接由赋值字面量的类型决定
{
let str = 'this is string'; // str: string
let num = 1; // num: number
let bool = true; // bool: boolean
}
//缺省显式类型注解的可变更的变量的类型转换为了赋值字面量类型的父类型,
//比如 str 的类型是 'this is string' 类型(这里表示一个字符串字面量类型)
//的父类型 string,num 的类型是 1 类型的父类型 number
//我们可以分别赋予 str 和 num 任意值(只要类型是 string 和 number 的子集的变量)
类型拓宽(Type Widening)
所有通过 let 或 var 定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显式添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。
let str = 'this is string'; // 类型是 string
let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
基于字面量类型拓宽的条件,我们可以通过如下所示代码添加显示类型注解控制类型拓宽行为。
{
const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'
}
对 null 和 undefined 的类型进行拓宽,通过 let、var 定义的变量如果满足未显式声明类型注解且被赋予了 null 或 undefined 值,则推断出这些变量的类型是 any:
{
let x = null; // 类型拓宽成 any
let y = undefined; // 类型拓宽成 any
/** -----分界线------- */
const z = null; // 类型是 null
/** -----分界线------- */
let anyFun = (param = null) => param; // 形参类型是 null
let z2 = z; // 类型是 null
let x2 = x; // 类型是 null
let y2 = y; // 类型是 undefined
}
类型缩小(Type Narrowing)
在 TypeScript 中,我们可以通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合,这就是 "Type Narrowing"。
{
let func = (anything: any) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else if (typeof anything === 'number') {
return anything; // 类型是 number
}
return null;
};
}
//同样,我们可以使用类型守卫将联合类型缩小到明确的子类型,具体示例如下:
{
let func = (anything: string | number) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else {
return anything; // 类型是 number
}
};
}
{
type Goods = 'pen' | 'pencil' |'ruler';
const getPenCost = (item: 'pen') => 2;
const getPencilCost = (item: 'pencil') => 4;
const getRulerCost = (item: 'ruler') => 6;
const getCost = (item: Goods) => {
if (item === 'pen') {
return getPenCost(item); // item => 'pen'
} else if (item === 'pencil') {
return getPencilCost(item); // item => 'pencil'
} else {
return getRulerCost(item); // item => 'ruler'
}
}
}
interface UploadEvent {
type: "upload";
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download";
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download":
e; // Type is DownloadEvent
break;
case "upload":
e; // Type is UploadEvent
break;
}
}
这种模式也被称为 ”标签联合“ 或 ”可辨识联合“,它在 TypeScript 中的应用范围非常广。
联合类型
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK
//联合类型通常与 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';
类型别名
//类型别名用来给一个类型起个新名字。类型别名常用于联合类型。
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用&
定义交叉类型。
{
type Useless = string & number;
}
//类型别名 Useless 的类型就是个 never
//交叉类型真正的用武之地就是将多个接口类型合并成一个类型,
//从而实现等同接口继承的效果,也就是所谓的合并接口类型,如下代码所示:
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}
如果同名属性的类型不兼容,比如上面示例中两个接口类型同名的 name 属性类型一个是 number,另一个是 string,合并后,name 属性的类型就是 number 和 string 两个原子类型的交叉类型,即 never,如下代码所示:
type IntersectionTypeConfict = { id: number; name: string; }
& { age: number; name: number; };
const mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
age: 2
};
type IntersectionTypeConfict = { id: number; name: 2; }
& { age: number; name: number; };
let mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ok
age: 2
};
mixedConflict = {
id: 1,
name: 22, // '22' 类型不能赋给 '2' 类型
age: 2
};
Interface
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
//可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
//只读属性用于限制只能在对象刚刚创建的时候修改其值。
//此外 TypeScript 还提供了 ReadonlyArray<T> 类型,它与 Array<T> 相似,
//只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
//有时候我们希望一个接口中除了包含必选和可选属性之外,
//还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
//需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: //number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
//上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,
//number 不是 string 的子属性,所以报错了。
//一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,
//则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number; // 这里真实的类型应该为:number | undefined
[propName: string]: string | number | undefined;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
接口与类型别名的区别
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。
Objects / Functions
两者都可以用来描述对象或函数的类型,但是语法不同。
//interface
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
//Type alias
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
//Other types
//与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement('div');
type B = typeof div;
与类型别名不同,接口可以定义多次,会被自动合并为单个接口。
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
扩展
//两者的扩展方式不同,但并不互斥。接口可以扩展类型别名,同理,类型别名也可以扩展接口。
//接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。
//接口扩展接口
interface PointX {
x: number
}
interface Point extends PointX {
y: number
}
//类型别名扩展类型别名
interface PointX {
x: number
}
interface Point extends PointX {
y: number
}
//接口扩展类型别名
type PointX = {
x: number
}
interface Point extends PointX {
y: number
}
//类型别名扩展接口
interface PointX {
x: number
}
type Point = PointX & {
y: number
}
视频链接:14_构造函数和this_哔哩哔哩_bilibili
typescript史上最强学习入门文章(2w字) - 掘金 (juejin.cn)