学习typescript,这一篇就足够了

1,675 阅读22分钟

ts.jfif

TypeScript介绍

  • TypeScript(简称:TS)是 JavaScript 的超集(JS 有的 TS 都有)。
  • TypeScript = Type + JavaScript(在 JS 基础之上,为 JS 添加了类型支持)。
  • TypeScript 是微软开发的开源编程语言,可以在任何运行 JavaScript 的地方运行

1. TS安装与编译

  • 第一步:全局安装ts和ts-node
// 全局安装ts
npm install typescript -g

// 全局安装ts-node
npm install ts-node@8.5.4 -g
  • 第二步:生成tsconfig.js配置文件
tsc --init 
  • 第三步:创建index.ts文件
const str: string = 'hello ts'
console.log(str)
  • 第四步:编译 ts 为 js 在控制台输入命令
tsc index.ts 

当前文件下会生成一个同名的index.js文件 这时候已经把ts文件编译成了一个js文件了; 但是这样非常的麻烦,每次都需要运行tsc ts文件名 生成js文件,再去使用node js文件名编译出结果。可以使用全局安装的 ts-node 直接编译 ts 文件

tsc-node index.ts // 输入 hello ts
// 这样运行后,修改ts文件后 ,帮我们自动编译成js文件
tsc --watch index.ts

2. TS原始类型

2.1 String 类型

let myName: string = 'lazy-zheng'

2.2 Number 类型

let age : number = 20 

2.3 布尔类型(boolean)

let isLoading: boolean = false

2.4 null类型

let n: null = null

2.5 undefined 类型

let u: undefined = undefined

2.6 symbol 类型

let s: symbol = Symbol()

2.7 数组类型

// 数组类型的两种写法,推荐使用写法1
// 写法1:
let numbers1: number[] = [1, 3, 5];
let strings1: string[] = ['a', 'b', 'c', 'd'];

// 写法2:
let numbers2: Array<number> = [2, 4, 6];
let strings2: Array<string> = ['e', 'f', 'g', 'h'];

2.8 联合类型

// 联合类型多个类型之间使用 | 隔开
let arr: (number | string)[] = [1, 3, 5, 'a', 'b'];

2.9 类型别名

// 当同一类型(复杂)被多次使用到,可以使用类型别名,简化该类型的使用,关键字为type
let arr1: (number | string)[] = [1, 3, 5, 'a', 'b'];
let arr2: (number | string)[] = [2, 4, 6, 'c', 'd'];

// 改造
type CustomArray = (number | string)[];
let arr3: CustomArray = [1,'a',3,'b'];
let arr4: CustomArray = [2,'c',4,'d'];

2.10 函数类型

function sum1(num1: number,num2:number):number {
  return num1 + num2; 
}
console.log(sum1(2, 6));

const sum2 = (num1: number,num2:number):number => {
  return num1 + num2; 
}
console.log(sum2(3, 2));

// 同时指定参数和返回值类型,这种写法仅支持箭头函数。
const sum3: (num1: number, num2: number) => number = (num1,num2) => {
  return num1 + num2; 
}
console.log(sum3(1,2));

// 如果函数没有返回值,那么函数的返回值类型为 void
function hello(name: string): void {
  console.log(`hello ${name}`);
}
hello('正');

// 函数可选参数
// 注意:可选参数必须放到参数的最后面,也就是说可选参数后面不能再出现必选参数
function mySlice(start?: number, end?: number): void {
  console.log('起始索引:', start, '结束索引:', end);
}
mySlice();
mySlice(1);
mySlice(3, 6);

2.11 对象类型

// 1.在一行代码中指定对象的多个属性类型时,使用;(分号)分割;
// 2.如果一行代码只指定一个属性类型(通过换行来分割多个属性类型),可以去掉;(分号);
let person: { uname: string; age: number; sayHello(): void } = {
  uname:'正',
  age: 18,
  sayHello() {
    console.log('Hello')
  }
}

// 可选属性的语法与函数可选参数的语法一致,使用?来表示
function myAxios(config: { url: string; method?: string }) {
  console.log(config);
}
myAxios({
  url:'123'
})

2.12 接口

// 当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
interface IPerson {
  name: string,
  age: number,
  sayHi(): void
}

const person1: IPerson = {
  name: '张三',
  age: 18,
  sayHi() {
    console.log(this.name,'person1')
  },
}

const person2: IPerson = {
  name: '李四',
  age: 16,
  sayHi() {
    console.log(this.name,'person2')
  }
}

接口和类型别名对比

  • 相同点:都可以给对象指定类型。
  • 不同点:
    • 接口,只能为对象指定类型。
    • 类型别名,可以为任意类型指定别名。
interface IPerson {
  name: string,
  age: number,
  sayHi(): void
}

type IPerson = {
  name: string,
  age: number,
  sayHi(): void
}

type NumAndStr = number | string

2.13 接口继承

interface Point2D {
  x: number,
  y:number
}

// 使用extends关键字实现继承
interface Point3D extends Point2D {
  z:number
}

const point: Point3D = {
  x: 1,
  y: 2,
  z: 3
}

2.14 元组

场景:在使用经纬度坐标来标记位置信息;可以使用数组来记录坐标,该数组中只有两个元素,并且这两个元素都是number类型。

// 这种方式可以满足需求,但是不够严谨
const position1: number[] = [39, 112];
// 元组类型时另一种类型的数组,它确切地指导包含多少个元素,以及特定索引对应的类型
const position2: [number, number] = [39.5516, 114.2238];

2.15 类型推论

在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型。

由于类型推论的存在,这些地方,类型注解可以省略不写。

发生类型推论的两种场景:

  1. 声明变量并初始化时
  2. 决定函数返回值时
// 此时ts自动推断出变量a为number类型
let a = 18;

// 此时不会触发类型推论
let b;
b = 1
b = '6'

// 函数的入参推荐必须指定类型,函数返回值会触发类型推论,推断出函数返回值的类型
function sum(num1: number, num2: number) {
  return num1 + num2;
}

2.16 类型断言

  1. 使用as关键字实现类型断言。
  2. 关键字as后面的类型是一个更加具体的类型(HTMLAnchorElement是HTMLElement的子类型)。
  3. 通过类型断言, link的类型变得更加具体,这样就可以访问a标签特有的属性或方法了。
const link = document.getElementById('link') as HTMLAnchorElement;
link.href = 'www.baidu.com';

2.17 字面量类型

使用模式:字面量类型配合联合类型一起使用。

使用场景:用来表示一组明确的可选值列表。

// 字面量类型
function changeDirection(direction:'up' | 'down' | 'left' | 'right') {
  console.log(direction);
}
changeDirection('down');

2.18 枚举

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。

枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。

// 枚举
// 枚举默认不设置值的时候,默认为当前枚举的索引
enum Direction{
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
function changeDirection(direction: Direction) {
  console.log(direction) // => 'LEFT'
}
changeDirection(Direction.Left)
  1. 其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)也就是说,其他类型会在编译为js代码时自动移除,但是,枚举类型会被比编译为js代码!
  2. 一般情况下,推荐使用字面量类型 + 联合类型组合的方式,因为相比枚举,这种方式更加直观,简洁,高效。

2.19 typeof运算符

js中提供了typeof操作符,用来在js中获取数据的类型

console.log(typeof 'hello ts')  // => string

实际上,ts中也提供了typeof操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。 使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。

let p = {
  x: 1,
  y: 2
};

function formaPoint1(point:{x:number,y:number}) {
  console.log(point)
}

// 这个写法等同于上面的写法
function formaPoint2(point: typeof p) {
  console.log(point);
}

let num: typeof p.x;  // => num变量的类型为number

注意:typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)

3.TS高级类型

3.1 class基本使用

class Person {
  // 没有默认值
  age: number;
  gender = '男';
  x = 1;
  y = 2;
  // 不允许给constructor设置返回值类型,否则报错提示
  constructor(age:number, gender:string) {
    this.age = age;
    this.gender = gender;
  }

  // 实例方法
  scale(n: number) {
    this.x *= n;
    this.y *= n;
  };
}
const p = new Person(18, '男');
p.scale(10)

3.2 class继承

// 1. extends关键字
class Animal {
  move() {
    console.log('走几步')
  }
}
class Dog extends Animal {
  name = '哈士奇';
  bark() {
    console.log('旺旺~')
  }
}

const d = new Dog();
d.move();
d.bark();
console.log(d.name);

// 2.implements 实现接口
interface Singable {
  myName: String,
  sayHi(): void,
}

class Person implements Singable {
  myName = '正';
  sayHi() {
    console.log('Say Hi')
  }
}
  1. 通过implements关键字让class实现接口。
  2. Person类实现接口Singable意味着,Person类中必须提供Singable接口中指定的属性和方法。

3.3 class类修饰符

// 1.class类的可见性修饰符
class Animal {
  constructor() {
  }
  //  public 公有的,公有成员可以被任意地方访问,默认可见性
  public move() {
    console.log('走几步~');
 }
  // protected 受保护的,仅对其声明所在类和子类中(非实例对象)可见
  protected run() {
    this.__jump__();
    console.log('跑起来~');
  }

  // private 表示私有的,只有在当前类中可见,对实例对象以及子类也是不可见的
  private __jump__() {
    console.log('跳起来~');
  }
}

class Dog extends Animal {
  // 属性前面加readonly关键字,当前属性就变为只读属性了,不可以被修改
  readonly age: number = 18;
  constructor(age: number) {
    super();
    this.age = age;
  }
  bark() {
    console.log('旺旺~')
    this.move();
  }
}

3.4 类型兼容性

两种类型系统:

  1. Structural Type System (结构化类型系统)
  2. Nominal Type System (标明类型系统)

TS采用的是结构化类型系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具有的形状,也就是说,在结构化类型系统中,对于对象类型来说,A的成员至少与B相同,则B兼容A(成员多的可以赋值给少的)。

对象的类型兼容性

// 两个类的兼容性演示:
class Point {
  x: number;
  y: number;
}

class Point2D{
  x: number;
  y: number;
  z: number;
}
// 这样编写不会报错
const p: Point = new Point2D();
  • 变量p的类型被标注为Point类型,但是,它的值确实Point2D的实例,并没有类型错误;
  • 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同;(都具有x,y属性,且属性类型也相同);
  • 接口interface之间的兼容性,类似于class,并且,class和interface之间也可以兼容;
  • 如果在Nominal Type System (标明类型系统)中(例如:C#、Java等)它们是不同的类,类型无法兼容。

函数之间的兼容性

  • 函数之间兼容性比较复杂,需要开率:1. 参数个数 2.参数类型 3. 返回值类型
  • 参数个数,参数多的兼容参数参数少的(或者说,参数少的可以赋值给多的)
// 1.参数个数:参数少的可以赋值给参数多的
type F1 = (a: number) => void;
type F2 = (a: number, b: number) => void;


let f1: F1 = (a:number) => {
  console.log(a)
};
let f2: F2;
f2 = f1;

// 错误演示
// f1 = f2;
  1. 参数少的可以赋值给参数多的,所以f1可以赋值给f2;
  2. 在js中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性
  3. 并且因为回调函数是有类型的,所以,TS会自动推导出参数item、index、array的类型。

返回值类型

// 原始类型
type F5 = () => string;
type F6 = () => string;

let f5: F5;
let f6: F6 = f5;

// 对象类型
type F7 = () => { name: string };
type F8 = () => { name: string, age: number };
let f7: F7;
let f8: F8
f7 = f8;
  1. 如果返回值类型是原始类型,此时两个类型要相同,比如,F5和F6;
  2. 如果返回值类型的对象类型,此时成员多的可以赋值给成员少的,比如,F7和F8;

3.5 交叉类型

交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)。

interface Person {
  name: string
};

interface Contact {
  phone: string
};

type PersonDetail = Person & Contact

let obj: PersonDetail = {
  name: 'zz',
  phone:'159********'
}
  1. 使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型

交叉类型(&)和接口继承(extends)的对比:

  • 相同点:都可以实现对象类型的组合。
  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
interface A{
  fn: (value: number) => string;
}

// 报错
interface B extends A {
  fn: (value: string) => string;
}

image.png

interface A {
  fn: (value: number) => string;
}
interface B {
  fn: (value: string) => string;
}
type C = A & B;
// C相当于 fn: (value: number | string)=> string

3.6 泛型的基本使用

泛型是可以在保证安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class

// 使用泛型来创建一个函数
function id2<Type>(value:Type): Type {
  return value;
}
// 1. 以number类型调用泛型函数
const num = id2<number>(18);
// 2. 以string类型调用泛型函数
const str = id2<string>('zz');
// 3. 以boolean类型调用泛型函数
const bool = id2<string>('zz');

// --
// 调用泛型函数的时候类型可以省略
let num1 = id(20);
let str1 = id('test');
  1. 泛型在保证类型安全(不丢失类型信息)的同时,可以让函数与多种不同的类型一起工作,灵活复用;
  2. 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用

3.7 泛型约束

添加泛型约束收缩类型,主要有两种方式:

  1. 指定更加具体的类型
function id<Type>(value: Type[]): Type[]{
 value.length;
 return value;
}
  1. 添加约束
interface ILength {
  length:number
}
function id<Type extends ILength>(value:Type) : Type {
  console.log(value.length, 'len');
  return value;
};

id(['a','b']);
id('test');
id({ length: 1, name: 'zz' });
  1. 创建描述约束的接口Ilenth,该接口要求提供length属性;
  2. 通过extends关键字使用该接口,为泛型(类型变量)添加约束;
  3. 该约束表示:传入的类型必须具有length属性

3.8 多个泛型变量的情况(使用keyof)

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)。

function getProp<Type,Key extends keyof Type>(obj:Type, key:Key) {
  return obj[key];
}
let person = { name: 'zz', age: 18, say:()=> {} };
getProp(person, 'name');
getProp(person, 'age');
getProp(person, 'say');

// 补充:几乎用不到这种场景
getProp(18, 'toFixed');
getProp('zz', 'split');
getProp('zz', 1);
getProp(['x', 'y', 'z'], 1);
  1. 添加了第二个类型变量Key,两个类型变量之间使用(,)逗号分隔;
  2. keyof关键字接收一个对象类型,生成其键名称(可能是字符串或者数字)的联合类型。

3.9 泛型接口

泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

interface IdFunc<Type> {
  id: (value: Type) => Type,
  ids: () => Type[]
}

let obj: IdFunc<number> = {
  id(value) {
    return value;
  },
  ids() {
    return [1,2,3]
  }
}
  1. 在接口名称后面增加<类型变量>,那么,这个接口就变成了泛型接口;
  2. 接口的类型变量,对接口中所有其他成员可见,也就是接口所有成员都可以使用泛型变量
  3. 使用泛型接口时,需要显式指定具体的类型。

3.10 泛型类

class GenericNumber <NumType>{
  defalutVlaue: NumType
  add: (x: NumType, y: NumType) => NumType
  
  constructor(value: NumType) {
    this.defalutVlaue = value;
  }
}
const myNum = new GenericNumber<number>(100);
// myNum.defalutVlaue = 10;
  1. 类似于泛型接口,在class名称后面添加<类型变量>,这个类就变成了泛型类;
  2. 当初始化类的时候传递的参数,此时可以省略调用时候的类型变量new GenericNumber(100);

3.11 泛型工具类型

  1. Partial<Type>用来构造(创建)一个类型,将Type的所有属性设置为可选,不会改变原来的类型
interface Props  {
  id: string
  children:number[]
}
type PartialType = Partial<Props>

let p1: Props = {
  id: 'abc',
  children: [1, 2]
}

let p2: PartialType = {  
}
  1. Readonly<Type>用来构造一个类型,将Type的所有属性设置为readonly(只读)
interface Props {
  id: string
  children:number[]
};
type ReadonlyProps = Readonly<Props>;

let p1: ReadonlyProps = {
  id: '1',
  children: [1, 2, 3]
}
// 重新给id属性赋值时,就会报错:无法分配到"id",因为它是只读属性
p1.id = '123'
  1. Pick<Type,Keys>从Type中选择一组属性来构造新类型
interface Props {
  id: number,
  title: string,
  content:string,
};
type PickProps = Pick<Props, 'title' | 'content'>;

let p1: PickProps = {
  title: 'zz',
  content: '内容'
};

(1)Pick工具类型有两个类型变量: 1 表示选择谁的属性 2 表示选择哪几个属性;
(2)其中第二个类型变量,如果只选择一个则只传入该属性名即可;
(3)第二个类型变量传入的属性只能是第一个类型变量中存在的属性;

  1. Record<Keys,Type>构造一个对象类型,属性键为Keys,属性类型为Type
type RecordObj = Record<'a' | 'b' | 'c', string[]>
let obj: RecordObj = {
  a: ['a'],
  b: ['b'],
  c: ['c'],
}

image.png (1)Records工具类型有两个类型变量:1 表示对象有哪些属性 2 表示对象属性的类型;
(2)构建的新对象RecordObj表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[];

3.12 索引签名类型

当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了;

interface AnyObject  {
  [key:string]:number
};
let obj: AnyObject = {
  a: 1,
  b: 2
};

interface MyArray<T> {
  [n: number]: T
}
let arr: MyArray<number> = [1, 3, 2];

(1)使用[key:string]来约束该接口中允许出现的属性名称,表示只要是string类型的属性名称,都可以出现在对象中;
(2)key只是一个占位符,可以换成任意合法的变量名称;
(3)MyArray接口模拟原生的数组接口,并使用[n:number]来作为索引签名类型;

3.13 映射类型

映射类型:基于旧类型创建新类型(对象类型),减少重复,提高效率;

type PropKeys = 'x' | 'y' | 'z';
type Type1 = { x: number, y: number, z: number };
// Type2等同于Type1
type Type2 = { [key in PropKeys]: number };

// 错误演示:
interface Type3 {
  [key in PropKeys]: number 
}

(1)映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[];
(2)Key in PropKeys表示Key可以是PropKeys联合类型中的任意一个,类似于for in(let k in obj);
(3)注意:映射类型只能在类型别名中使用,不能在接口中使用;

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建:

type Props = { a: number, b: string, c: boolean };
type Type1 = { [key in keyof Props]: string };

(1)先执行keyof Props获取到对象类型Props中所有键的联合类型即,'a'|'b'|'c';
(2)然后,key in...就表示Key可以是Props中所有的键名称中的任意一个;

3.14 索引查询类型

type Props = { a: number, b: string, c: boolean };
type TypeA = Props['a'];

// 实现Partial工具
type MyPartial<T> = {
  [P in keyof T]?: T[P];
}
type  MyPartialProps = MyPartial<Props>

// 其他使用方式:同时查询多个类型
type TypeB = Props['a' | 'b'];
// keyof Props返回所有属性的的类型
type TypeC = Props[keyof Props];

(1)用到T[P]语法,在TS中叫做索引查询(访问)类型
(2)Props['a']表示查询类型Props中属性'a'对应的类型number,所以TypeA的类型为number;

4. 类型声明文件

4.1 类型声明文件概述

  • 在项目开发中使用第三方库时,你会发现它们几乎都有相应的TS类型,这些类型是怎么来的呢?类型声明文件
  • 类型声明文件:用来为已存在的JS库提供类型信息这样在TS项目中使用这些库时,就向用TS一样,都会有代码提示、类型保护等机制了。
  • TS中有两种文件类型:1.ts文件 2. .d.ts文件

.ts文件:

  1. 既包含类型信息又可执行代码;
  2. 可以被编译为.js文件,然后,执行代码;
  3. 用途:编写可执行的程序代码;

.d.ts文件:

  1. 只包含类型信息的类型声明文件;
  2. 不会生成.js文件,仅用于提供类型信息
  3. 用途:为js提供类型信息;

总结:.ts是implementation(代码实现文件);.d.ts是declaration(类型声明文件);

4.2 类型声明文件的使用说明

  • 创建自己的类型声明文件:1. 项目内共享类型 2. 为已有JS文件提供类型声明
  1. 项目内共享类型:如果多个.ts文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
// index.d.ts
type Props = { x: number, y: number };
export {
  Props
}

// a.ts
import { Props } from './index';
let p1: Props = {
  x: 1,
  y: 2,
}

// b.ts
import { Props } from './index';
let p2: Props = {
  x: 3,
  y: 4,
}
  1. 为已有JS文件提供类型声明:在将js项目迁移到ts项目时,为了让已有的.js文件有类型声明。
// utils.js
let count = 10;
let songName = '探故知';
let position = {
  x: 0,
  y: 0
}
function add(x, y) {
  return x + y;
}
function changeDirection(direction) {
  console.log(direction);
}
const fomartPoint = point => {
  console.log(point);
}
export default {
  count,
  songName,
  position,
  add,
}
// utils.d.ts
declare let count: number;
declare let songName: string;
interface Point {
  x: number,
  y: number
};
declare let position: Point;
declare function add(x: number, y: number): number

declare function changeDirection(direction: 'up' | 'down' | 'left' | 'right'): void;

type FomartPoint = (point: Point) => void;
declare const fomartPoint: FomartPoint;
// 注意:类型提供好之后,需要使用模块化方案中提供的模块化语法,来导出声明好的类型,然后才能在.ts文件中使用。
export {
  count,
  songName,
  position,
  add,
  changeDirection,
  fomartPoint
}

说明:在导入.js文件时,TS会自动加载与.js同名的.d.ts文件,以提供类型声明。 declare关键字:用于类型声明,以其他地方(比如,.js文件)已存在的变量声明类型,而不是创建一个新的变量。

  1. 对于type interface等这些明确就是TS类型的(只能在TS中使用的),可以省略declare关键字
  2. 对于let function等具有双重含义(在js,ts中都能使用),应该使用declare关键字,明确指定此处用于类型声明。

5. 在React中使用Typescript

5.1 使用CRA创建支持TS的项目

命令: npx create-react-app 项目名称 --template typescript 相对于非TS项目,项目结构主要有以下三个变化:

  1. 项目根目录增加了tsconfig.json配置文件:指定TS的编译选项(比如:编译时是否移除注释)。
  2. React组件的文件扩展名变为:*.tsx。
  3. src目录增加了react-app-env.d.ts:React项目默认的类型声明文件。

image.png react-app-env.d.ts:React项目默认的类型声明文件。

三斜线指令:指定依赖的其他类型声明文件,types表示依赖的类型声明文件包的名称。

image.png 解释:告诉TS帮我们加载react-scripts这个包提供的类型声明。
react-scirpts的类型声明文件包含了两部分类型:

  1. react,react-dom,node的类型
  2. 图片,样式等模块的类型,以允许在代码中导入图片,svg等文件。

image.png TS会自动加载该.d.ts文件,以提供类型声明(通过修改tsconfig.json中的include配置来验证)。表示会把src目录下所有的tsx文件交给TS来处理

5.2 TS配置文件tsconfig.json

tsconfig.json指定:项目文件和项目编译所需的配置项。

注意:ts的配置项非常多(100+),以CRA项目中的配置为例来学习。

  1. tsconfig.json文件所在目录为项目根目录(与package.json同级)
  2. tsconfig.json可以自动生成,命令:tsc --init
// [tsconfig文档链接](https://www.typescriptlang.org/tsconfig)
{
  // 编译选项
  "compilerOptions": {
    // 生成代码的语言版本
    "target": "es5",
    // 指定要包含在编译中的library
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    // 允许 ts 编辑js文件
    "allowJs": true,
    // 跳过声明文件的类型检查
    "skipLibCheck": true,
    // es 模块 互操作,屏蔽ESModule 和 CommonJs之间的差异
    "esModuleInterop": true,
    // 允许通过 import x from 'y' 即使模块没有显式指定default导出
    "allowSyntheticDefaultImports": true,
    // 是否开启严格模式
    "strict": true,
    // 对文件名强制区分大小写
    "forceConsistentCasingInFileNames": true,
    // 为Switch 语句启用错误报告
    "noFallthroughCasesInSwitch": true,
    // 生成代码的模块化标准
    "module": "esnext",
    // 模块解析(查找)策略
    "moduleResolution": "node",
    // 允许导入扩展名为.json模块
    "resolveJsonModule": true,
    // 是否将没有import/export的文件视为旧(全局而非模块化)脚本
    "isolatedModules": true,
    // 编译时不生成任何文件(只进行类型检查)
    "noEmit": true,
    // 指定将 jsx 编译成什么形式
    "jsx": "react-jsx"
  },
  // 指定允许 ts 处理的目录
  "include": [
    "src"
  ]
}

5.3 React中常用类型

react是组件化开发模式,react开发主要任务就是写组件,两种组件:1. 函数组件 2. class组件

1.函数组件,主要包括以下内容:

  • 组件的类型
  • 组件的属性(props)
  • 组件属性的默认值(defaultProps)
  • 事件绑定和事件对象

函数组件的类型以及组件的属性

// 写法1:
import { FC } from 'react';
/**
 * 函数组件 
 * 组件的类型
 * 组件的属性(props),包括可选属性
 */
type Props = { uname: string; age?: number }
// FC 是react提供的泛型类型
const Hello: FC<Props> = ({ uname,age}) => (
  <div>你好我叫:{uname} 今年:{ age}</div>
)

/**
 * 写法2:(推荐)
 * 说明:完全利用JS(TS) 自身的能力来编写组件
 * 函数组件 
 * 组件的类型
 * 组件的属性(props),包括可选属性
 */
type Props = { uname: string; age?: number }
const Hello = ({ uname,age}:Props) => (
  <div>你好我叫:{uname} 今年:{ age}</div>
)

function App() {
  return (
    <div>
     <Hello uname="rose" age={18}></Hello>
    </div>
  )
}

组件属性的默认值(defaultProps)

// 写法1:
type Props = { uname: string; age?: number }
const Hello = ({ uname,age}:Props) => (
  <div>你好我叫:{uname} 今年:{ age}</div>
)
// 给Hello组件设置age默认值为20
// 我们如果传入了age则已传入的为准,反之取默认值
Hello.defaultProps = {
  age:20
}

// 写法2:
type Props = { uname: string; age?: number }
// 直接在对象属性结构的时候为age赋值20为默认值,如果传递了age则已传递的为准
const Hello = ({ uname,age = 20 }:Props) => (
  <div>你好我叫:{uname} 今年:{ age}</div>
)
function App() {
  return (
    <div>
     <Hello uname="rose" age={16}></Hello>
    </div>
  )
}

事件绑定和事件对象

type Props = { uname: string; age?: number }
const Hello = ({ uname, age = 20 }: Props) => {
  // 需要指定实践对象,这里才可以访问到e
  const onclick = (e:React.MouseEvent<HTMLButtonElement>) => {
    console.log('赞~',e.currentTarget)
  }
  // 需要指定实践对象,这里才可以访问到e 
  const onChange = (e:React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value,'e')
  }
  return (
    <div>
      <div>你好我叫:{uname} 今年:{age}</div>
      <button onClick={onclick}>点赞</button>
      <input placeholder='请输入' onChange={onChange}></input>
    </div>
  )
}
function App() {
  return (
    <div>
     <Hello uname="rose" age={16}></Hello>
    </div>
  )
}

image.png class组件的类型

type State = { count:number }
type Props = { message?: string }
class C1 extends React.Component {} // 无Props 无state
class C1 extends React.Component<Props> {} // 有Props,无state
class C1 extends React.Component<{},State> {} // 无Props,有state
class C1 extends React.Component<Props,State> {} // 有Props,有state

class组件的属性和属性默认值

/**
 * class组件
 * 组件类型
 */
type Props = { name: string, age?: number }
class Hello extends React.Component<Props> {
  // 写法1:
  static defaultProps: Partial<Props> = {
    age:18
  }
  render () {
   const { name,age } = this.props
    return (
      <div>你好我叫:{name },今年年龄{ age }了</div>
    )
  }
}

/**
 * class组件
 * 组件类型
 */
type Props = { name: string, age?: number }
class Hello extends React.Component<Props> {
  // (推荐)写法2:
  render () {
   // 简化class组件的属性默认值   
   const { name,age = 20 } = this.props
    return (
      <div>你好我叫:{name },今年年龄{ age }了</div>
    )
  }
}
function App() {
  return (
    <div>
      <Hello name='jack'></Hello>
    </div>
  )
}

class组件状态(state)和事件

// 定义类型
type State = { count: number }
// 提供第二个类型参数,方便在setState的时候有提示
class Counter extends React.Component<{}, State> {
  state: State = {
    count: 0
  }
  handleClick = () => {
    this.setState({
      count:this.state.count +1
    })
  }
  render() {
    return (
      <div>
        <span>计算机:{ this.state.count}</span>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}
function App() {
  return (
    <div>
      <Counter></Counter>
    </div>
  )
}