TypeScript

编程语言的类型

动态类型语言

运行期间做数据类型检查 js ruby python

静态类型语言

编译期间类型检查 c c++ java

typescript

冒号后面的都是类型

特性

  • typescript 是 js 的超集
  • 拥有静态类型
  • 需要编译成 js 运行

优势

  • ts 的静态类型可以在开发过程中,发现潜在问题
  • 更好的编辑器提示
  • 通过静态类型的声明,代码清晰易读
1
2
3
4
5
6
7
// js中变量是动态类型 可以随时改变类型
let a=1;
a='str'

// ts中是静态类型 改变类型会报错
let b=1;
b='str'

类型注解 & 类型推断

ts 自动启用类型推断,判断变量类型

如果能分析变量类型不需要类型注解,否则需要

基础类型 & 对象类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基础类型 string number boolean symbol void null undefined
let count: number;


// 对象类型 {} [] function Class
const teacher: {
name: string;
age: number;
} = {
name: 'jason',
age: 28,
};

const numbers: number[] = [1, 2, 3];

class Person {}
const p: Person = new Person();

const getTotal: () => number = () => {
return 123;
};

联合类型

1
let myName: string | number;

交叉类型

将多个类型合并成一个类型

1
2
type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>
type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>

Partial

把一些属性变为可选的

1
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>

枚举

1
2
3
4
5
6
7
8
9
10
enum TabTypes {
Case = 'case',
Ganged = 'ganged',
}
const [currentTab, setCurrentTab] = useState(TabTypes.Case);
if (currentTab === TabTypes.Case) {
setCaseSource(insertKeys(res.data));
} else {
setGangedSource(insertKeys(res.data));
}

函数注解

函数的入参需要类型注解,返回值如果可以类型推断的话不需要写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 函数入参及返回值注解 c:可选参数只能放最后
function add(a: number, b: number, c?: number): number {
return a + b;
}
// 函数表达式
const add2: (x: number, y: number) => number=(a,b)=>{
return a+b
}
// interface写法
interface IFn {
(x: number, y: number): number;
}
const add2: IFn = (a, b) => {
return a + b;
};
// type写法
type fn = (x: number, y: number) => number;
const add2: fn = (a, b) => {
return a + b;
};
// 函数解构的写法
function add2({ a, b }:{a:number, b: number}): number {
return a + b;
}
add2({ a: 1, b: 2 });
const total = add(1, 2);

// 无返回值
function sayHello(): void {
console.log('hello');
}

// 函数永远不会执行完
function errorEmitter(): never {
throw new Error('error');
console.log('end');
}

数组注解

1
2
3
4
5
6
7
8
9
10
11
12
13
// 数组注解 能推断出来的不需要注解 直接的赋值的数组ts可以类型推断出来
const arr1: number[] = [1, 2, 3]; // 只能是number
const arr2: (number | string)[] = ['1', 2, 3]; // 可以是number或string
const arr3: undefined[] = [undefined, undefined]; // 只能是undefined

// 对象数组
const arr4: { name: string; age: number }[] = [{ name: 'a', age: 18 }];
// 类型别名 对上面的另一种写法
type User = { name: string; age: number };
const arr5: User[] = [{ name: 'a', age: 18 }];

// 元组 tuple 一个数组的长度固定 元素顺序类型固定
const info: [string, string, number] = ['andy', 'male', 20];

interface & type

  • interface是数据的共性的抽象 有自己的属性 只是在开发过程中做语法提示校验的工具 编译后不存在

    • 对对象的形状(shape)的描述
1
2
3
4
5
6
7
8
9
10
11
interface Person {
readonly name: string; // 只读 该属性再写会报错
age?: number; // 可选属性 可有可无
[propName: string]: any; // 将来多出的其他属性string类型也是可以的
say?(): string; // 方法属性 返回值string
say: ()=>{}
}
// 接口继承
interface Teacher extends Person {
teach(): string; // 自己的属性
}
    • 定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13
// interface 定义函数类型
interface ISayHi {
(word: string): string;
}
const say: SayHi = () => {
return 'hi';
};

interface IModalProps {
visible: boolean; // 是否可见
handleClose: () => void; // 隐藏自身
form: any;
}
    • 对类(class)进行抽象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Radio{
switchRadio():void
}
interface Battery extends Radio{
checkBatter():void
}
// 类实现interface
class Car implements Radio{
switchRadio() {

}
}
class Phone implements Battery{
switchRadio() {

}
checkBatter() {

}
}
  • type 类型别名 只是简单的别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 常用于
type fnType = (a: string) => string
type FooType = string | fnType

type Person = {
name: string;
age?: number;
};
function getName(person: Person): void {
console.log(person.name);
}
const setName = (person: Person, newName: string) => {
person.name = newName;
return person;
};
// 传对象引用和对象字面量的校验结果不一样 前一种无强校验
let p = { name: 'andy', sex: 'male', say() {} };
getName(p);
getName({ name: 'andy', sex: 'male' });
setName({ name: 'jason' }, 'andy');

类型断言

联合类型时 ts 只拿到公有的一些方法 使用类型断言联合类型的某一种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getLength(input: string | number){
// 这里会报错
if(input.length){
return input.length
}
}
function getLength(input: string | number){
// 类型断言 告诉ts我知道他是什么类型
const str = input as String
if(str.length){
return str.length
}
}
function getLength(input: string | number){
// 另一种写法
if((<string>input).length){
return (<string>input).length
}
}

ts 给 es6 的类增加了访问修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Person {
/**
* public 允许在类的内外调用
* private 允许类内不允许子类
* protected 允许类内及继承的子类
* readonly 只能读不能写
* */
// 简写
constructor(private _name: string, public age: number){}
// 常规写法
// public name: string;
// constructor(name) {
// this.name = name;
// }
// getter 属性
get name() {
return this._name + 'geted';
}
// setter 属性
set name(value: string) {
this._name = value
}
}

const p = new Person('nn', 18);
console.log(p.name);
p.name = 'change';
console.log(p.name);


// ts 实现单例模式
class Demo{
// 私有静态属性
private static instance: Demo;
// 私有constructor
private constructor(public name: string){};
// 公共静态方法
static getInstance(){
if(!this.instance){
this.instance = new Demo('lee')
}
return this.instance
}
}
// 调用的是同一个instance
const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1, demo2)

泛型

泛型,泛指的类型, 定义函数、接口和类时使用占位符不指定具体类型,使用时才指定,用<>先声明, 声明泛型 T 可以任意命名函数泛型,代表不知道什么类型的同一种类型

  1. 函数泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 常规写法
function plus(a: number, b): number {
return a + b;
}
const a = plus
// interface写法
interface IPlus{
(a: number, b: number): number
}
const plus:IPlus=(a,b)=>{
return a + b;
}
const a = plus(2,2)
// 泛型写法
function join<T>(first: T, second: T): T {
return `${first}${second}`;
}
// 可以声明 也可以类型推断
join<number>(1, 1);
join(1, 2);
function join<T, P>(first: T, second: P) {
return `${first}${second}`;
}
  1. 接口泛型
1
2
3
4
5
6
7
8
9
10
11
interface IPlus<T> {
(a: T, b: T): T;
}
const plus: IPlus<number> = (a, b) => {
return a + b;
};
const a = plus(1, 2);
const connet: IPlus<string> = (a, b) => {
return a + b;
};
const b = connet('hello', 'ts');
  1. 类泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Queue<T>{
private data=[]
push(item: T){
return this.data.push(item)
}
pop(): T{
return this.data.pop()
}
}

const q1= new Queue<number>()
q1.push(1)
q1.push(2)
console.log(q1.pop().toFixed());

const q2= new Queue<string>()
q2.push('ss')
console.log(q2.pop().trim())

泛型约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 泛型约束 使用extends使泛型满足某些条件
interface IWithLength{
length: number
}
function echo<T extends IWithLength>(args: T):T{
// 需要一个有length属性的泛型
console.log(args.length);
return args
}
const res = echo('sss')
const res2=echo([1,2,3])

// 泛型继承 使用extends扩展类型
interface IJoin {
name: string;
}
function join<T extends IJoin | number>(data:T[]) {
return data[0].name;
}
join([{ name: 'sss' }]);

声明文件

第三方库使用时需要声明文件

声明文件以.d.ts 结尾 如:jQuery.d.ts 有了声明文件后项目中就可以使用 jQuery() 提供语法提示且不报错

搜索第三方库的声明文件 TypeSearch

从 npm 安装第三方库的声明文件 npm @types

1
2
// jQuery.d.ts
declare var jQuery: (selector: string) => any;

typescript编译

ts-node 编译执行.ts 文件

1
2
npm install -g ts-node
ts-node script.ts

tsc

  • 不带任何输入文件的情况下调用 tsc,编译器会从当前目录开始去查找 tsconfig.json 文件,逐级向上搜索父目录。
  • 不带任何输入文件的情况下调用 tsc,且使用命令行参数–project(或-p)指定一个包含 tsconfig.json 文件的目录。
  • 当命令行上指定了输入文件时,tsconfig.json 文件会被忽略。
1
2
3
4
5
6
7
8
9
10
11
# Run a compile based on a backwards look through the fs for a tsconfig.json
tsc

# Transpile just the index.ts with the compiler defaults
tsc index.ts

# Transpile any .ts files in the folder src, with the default settings
tsc src/*.ts

# Transpile any .ts files in the folder src, with the compiler settings from tsconfig.json
tsc --project tsconfig.json src/*.ts

tsconfig

  • 生成tsconfig.json
1
tsc --init
  • 使用指定的tsconfig.json编译
1
2
3
4
// pacage.json
"script":{
"build-ts": "tsc -p tsconfig.build.json"
}
  • tsconfig配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// tsconfig.json
{
"compilerOptions": {
/*=== Basic Options 基础编译选项 ===*/
// "incremental": true, /* Enable incremental compilation */
"target": "ES5", /* 编译完之后的ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. 但是新的内置对象、静态方法、实例方法等不转换 */
"module": "ESNext", /* 使用怎样的模块形式输出: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, // default:false 允许编译javascript文件。
// "checkJs": true, /* Report errors in .js files. */
"jsx": "preserve", // Default :"preserve"在 .tsx文件里支持JSX 在preserve模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel) "react" 转换成React.
"declaration": true, /* 为每一个ts文件生成'.d.ts' 文件. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "build", /* 输出目录 */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* 文件必须以模块的形式组织 */


/*=== Strict Type-Checking Options 严格模式检查 ===*/
"strict": true, /* default:false 启用所有严格类型检查选项。相当于全部启用下面这些 */
// "noImplicitAny": true, /* 不能有隐式的any类型 */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */


/*=== Additional Checks 一些格式检查===*/
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */


/*=== Module Resolution Options 模块相关的一些方案===*/
"moduleResolution": "node", /* 指定模块解析策略为node 否则会绝对路径去找找不到: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true, /* 允许使用 import React from "react"; 否则要写成:import * as React from "react"; */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */


/*=== Source Map Options source map配置===*/
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/*=== Experimental Options 实验性选项支持 ===*/
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Default:false 启用实验性的ES装饰器。 */

/*=== Advanced Options 高级选项 ===*/
"forceConsistentCasingInFileNames": true, /* Default:false 禁止对同一个文件的不一致的引用 */
// "skipLibCheck": false, /* default:false 忽略所有的声明文件( *.d.ts)的类型检查,适用于node_modules中有两个同样的库 */
},

/*=== File Inclusion 文件包含 ===*/
"include": ["src"],
"exclude": ["src/**/*.tsx","test", "stories"]
}

typescript + React

常规写法

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';

interface IHelloProps {
name: string
}

const Hello = (props: IHelloProps) => {

return <h2>{`Hello ${props.name}`}</h2>;
};

export default Hello;

使用 react 提供的声明

/node_modules/@types/react/index.d.ts 声明文件

1
2
3
4
5
6
7
8
9
...
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { FC, ButtonHTMLAttributes,
AnchorHTMLAttributes } from 'react';

// 为了允许我们的组件支持原生props 结合react提供的button、a原生props
type NativeButtonProps = IButtonProps & ButtonHTMLAttributes<HTMLElement>
type AnchorButtonProps = IButtonProps & AnchorHTMLAttributes<HTMLElement>

interface IHelloProps {
name?: string;
}
// 提供了react函数组件的各种特性 如children displayName等
// 不然会报错 React.FC 是 React.FunctionComponent 的别名
const Hello: React.FC<IHelloProps> = (props) => {
return (
<h2>
{`Hello ${props.name}`}
{props.children}
</h2>
);
};
Hello.defaultProps={
name: 'ts'
}
export default Hello;