咱们研制开源了一款基于 Git 进行技能实战教程写作的东西,咱们图雀社区的一切教程都是用这款东西写作而成,欢迎 Star 哦

假如你想快速了解怎样运用,欢迎阅读咱们的 教程文档 哦

学习} a D e ~ d 6 0 =了注解函数,又了解了类型运算如联合类型和穿插类型,接下来咱们来了解一些 TS 中独有的类型别号,它相^ U R似 JS 变量,l 7 M y y #是类型变量,接着咱们还会学习 TS 中内3 k x P ~ Y 2 ]容十分庞杂的内容之一:类,了解 TS 中类的独有特性,以及怎样注解类,乃至用类去注解其他内容。

欢迎阅读 类型即正义,2 C g / S K v PTypeScript 从入门到实践系列:

  • 《类型即正义:TypeScript 从入门到实践(序章)》:用 TS 初始^ I J化一个 ReX s c # i U F f [act 项目
  • 《类型即正义:TypeScript 从入门到实践(一)》:解说根底的 TS 类型和接口
  • 《类型即正义:TypeScript 从入门~ 5 T / T B Q } ;到实践(二)》e ~ i:解说怎样注解函数w L Z R l、高级类型以及类型护卫

本文所触及的源代码都放在了 Github 或许 Gitee 上,假d { P如您觉得咱们写u i Z得还不错,希望您能给❤️这篇文章点赞+Git| _ % }hubGitee库房加星❤️哦~

此教程归于 Reac2 v j 6 k U Tt 前端工程师学习道路的一部分,欢迎来 S* S Z mtar 一波,鼓励咱们持续创作P 9 U !出更好的教程,持续更新中a g { e + 0~

运转代码

假如你偏爱 码云,那么你能够运转如下命令获取这1 ? { P一步的代码,然后你能够跟着文章的内容将代码做出修正:

git clone -b part-three https://gitee.com/tuture/typescript-tea.git
cd typescript-tea && npm install && npm start

假如你偏爱 Github,那么你能够运转如下命令来获取初始代码:

git clone -b part-thre git@github.com:tuture-dev/typescript-tea.git
cd typescriptl B + M-tea && npm install && npm start

! E C & a E y 0 }型别号

就像咱们为了在平常开发中愈加灵敏而创立变量或许干掉硬编码数据相同,TS 为咱们提供了类型别号,它允许你为类型创立一个名字,这个名字便是类型的别号,从而h # u : 6 ^ E 4你能够在多处运用这个别号,并且有必要的时分,你能够更改别号的值(类型),以到达一次替换,多处运用的效果。

咱们来看一个简略的类型别号的比方,假如咱们% ( p有一个获取G s N % : w一个人名字的函数,它接纳一个参数,这个参数有可能直接是要获取的名字,它是一个 stringN a L q 类型,也有可能是一个别的一个y [ %函数,需求调用它以获取名字,它是一个_ R E R Y –函数类型,咱们来看一下这个比方:

function getName5 2 W %(n) {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}

假如咱们要给这个 n 进行类型注解,那么它应该一同是 string | () =>t 5 [ q; s! u 7 ( 7 + T n Dtring ,是 string 类型和 (^ - b & 9 l h .) => string 函数类型的联合类型,有过必定开发经验的同学可能会发觉,这样写可能很影响原代码的可读性,并且这个 n 的类型可能会改变,由于咱们的函数可能扩展,所以假如咱们用一个类型别号把这个 n 的类型表明出来,那么就相似咱们用变量代替了硬编码,B N q u可扩展性就更强了,咱们马上来测验一下:

type NameParams = 'string' | () => 'stB ? M Z Gring';
function getName(n: NameParams~ _ I p): strint l d 3g {
/Z / R/ ... 其它相同
}

能够看到咱们用了一个 NamePara( 2 / j c T O Bms 类型别号,N 1 Y k e n它保存着原i l , p联合类型,类型别号便是等g j [号左边是 type 关键字加上别号变量,等号右边是带保存的类型,这个类M } ^ n 0 0型很广,它能够是字面量类型,根m N w l jg q s r { i类型,元组、函数、联合类型和穿插类型、乃至还能够是其他类型别号的组合。

所以对于上面的 NameParams ,咱们能够进一步拆解它为如下的姿态:

type Name = string;
type NameResolver = () => string;
type NameParams = Name | NameResolver;
function getName(n: NameParams):U S @ 4 X + Name {
// ... 其他相同
}

咱们看到,w . ` d上面这个不仅愈加细粒度,咱们将 NameParams 拆成了两个类型别号:NameNameResolver ,别离处理 string() => string 的情况,然后经过联i # T Q合操作符联合赋值给 NameParams ;还带来了一个优势,咱们的回来值能够愈加清晰便是 Name 类型,这样 Name 改变,它可能变成 number 类型,那么也能很好的反应这个改变,且只需求修_ 5 8 N正一下 Name 的值为 nuk q 3 pmber 类型就能够了,一切其他的 Name 类型会主动改变。

类型别号与接口

有同学读到这儿,可能有疑问了,这个类型别号貌似无所不能嘛,那它和接口有什么区别了?

接口主要是用来界说一个结构的类型,比方界说一个目标的类型,而类型别号能够是恣意细粒; 1 = j * ( i I度的类型界说,比方咱们前面讲的最原子的字母量类型如 'hello tuture' 类型,到目标类型如:

type tV 9 ! Kuture = {
tuL c q ntureCommunit` J X 5 # ^ d vy: string;
editure:l 5 p strY & K ! !ing;
tutureDocs: string;
}

上面这个类型咱们界说了一个包括三个特点的目标类型,并U w M +tuture 别号来存储它们。

界说上面这个目标的类型咱们能够用之前学到的接口这样写:

interface Tuture {
tutureCommunity: st[ V # t $ 4 Xring;
editure: string;
tutureDocs: string;
}

能够看到类型别号既能够表达接口所表达的类型,还比接口愈加细粒度,它还能够是一个根底类型如 tyK 2 3 [ y m E * Qpe name = 'string'

着手实践

还记得之前咱们那个 src/TodoList.ts$ $ l dxAction 组件的 onClick 办法的参数 key 嘛?它是一个联合类型类型 "complete | delete" ,咱们在多出处用到它,现在咱们是硬编码写在了程序里,未来这个 key 可能会改变,所以咱们需求换成类型别号来o Z s * # 4 u E ^表达它们,打开 src/TodoList.tsxG ^ P l p ? F= } T A w ^ : 7 x对其间的内容作出对应的修正如下:

import React from "react";
import { List, Avatar, Menu, Dropdown } frG O E w 7om "antd";
import {H : ( } . 0 p DownOutlined } from "@af N ` $ L c Xnt-design/icons";
impor1 E s V i F r ~ Gt { ClickParam } from "antd/lib/u / C S y t $menu";
import { Todo, getUserById } f+ i Hrom "./utils/data";
type MenuKey = "complete" | "delete";
interface ActionProps {
onClick: (key: MenuKey) => void;
isg k 8 1 5 3 B iCompleted: boolean;
}
// ...
iB H j y ; Qnterface TodoListProps {K 0 k v j U
todoList: Todo[];
onClick: (todoId: string, key:K 2 % X MenuKey) =>! 0 Z v| C v 7oid;
}
functio3 & p . Zn TodoList({ todoList, onClick }: TodoListProps)s + = V i i {
return (
<List
className="demo-loadmore-list"
itemLayout="horizontal"
dataSource={todoList}
r[ + + oenderItem={item => {
const user = getUserById(item.user);
return (
<List.I$ { y y | q v rtem
key={item.id}
actions={[b  l Q Y S q
<Dropdown
overlay={() => (
<Action
isCompleted={item.isCompleted}
onClick={(key: MenuKey) => onClick(item.id, key)}
/&gj $ M ; Z Kt;
)}) Y L b Q f N  `
>
<a key="list-loadmore-more">
操作 <DownOutlined />
</a>
</Dropdown>
]}
>
<List.Item.Meta
avatar={<Avatar src={user.avatar} />}
title={<a href="https://ant.design">{user.name}</a>}
description={item.V j E i y R _date}
/>
<div
style+ D D V={{
textDecoration: item.isCompleted ? "line-through" : "none"
}}
>
{item.conJ [ t |tent}
</div>
</List.Item>
);
}}
/>
);
}
export defaJ q / t l L B 5 gult TodoList;

能够看到,咱们界说了一个 Menu$ d ` 7 : q U nKey 类型别号,G t O q 3 @ I W它表明原联合类型 complete | delete ,然后咱们替换了组件中三处运用到这个联合类型的 onClick 函数的参数} t 1 M W key ,将其用 MenuKey 来注解。

其次咱们还删除了 antd@ant-design/icons 里边2 K { – J的剩余导出。

持续改进

接着咱们再来对 TodoList 做一点改变,L I 2导出一下咱们刚刚界说的 MenuKey ,由于还有其他的当地运用到它,咱们打开 src/TodoList.tsxMenuKey 添加 export 前缀,导出咱们的类型别号:

// ...
import { Todo, getUser9 5 ^ oById } from "./u ; D { Ptils/data";
exporti 2 @ I { Y { type MenuKey = "comple3 D J # Vte" | "delete";
interface ActionProps {
onClick: (key} / _ F: MenuKey) => void;
isCompleted: boolean;
}
// ...

接着咱们在 src/App.t! M e R t } } csx 里边导入咱们的 MenuKey 类型别号,并替换对应的 onClick 的参数 key 的类型注解为 MenuKey

import React, { useRef, useState } from "react";
import { Button, TypograpI 6 { #hy, Form, Tabs } from "antd";
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
import { toC j E ] * doListData } from "./utils/data"2 Y o ( } # * B Y;
import { MenuKey } from "./TodoList";
import "./Appq m E *.css";
import logo froT x } [ 4 ! $ u Nm "= V l G 1./logo.svv 1 g 9 P 0 } 1 yg";
// ...
function App() {
const [todoList, setTodoList] = useState(todoListDaM , 7 7 Eta);
// ...
const activeTodoList = todoList.filter(todo => !todo.isCompleted);
consp } . Y qt completedTodoList = todoList.O & 8 0 Nfilter(todo => tor * r @ Zdo.isCompleted);
const onClick = (todoId: string, kel 9 E jy: Mi 5  .enuKey) => {
iT , n 4 . M / f (key === "complete") {
const newTodoList = todoList.map(todo => {l 8 E
if (todo.id === todoId) {
return { ...todo, isCompleted: !todoo n , H 6 !.isCompleted };
}
return todo;
});
setTodoList(newTodoList);
} else if (key === "delete") {
cons^ H k . c k lt newTodoList = toda ! h +oList.filter(todo => todo.id !== todoId);{  b
setTodoList(newTodoList);
}
};_ O v  0
// ...
return (
&7 1 , , * $ X ) klt;div className="i 8 H E D ; - App" ref={ref}>
//I c G ( 5 $ B ...
</div>
);
}
export default Appk H !;

能够看到如上文件里边,咱们还删除了一些 antd 里边不必要的包导入。

小结

这一节} H H ` B咱们学习了类型别号,它能够在必定程度上模仿接口(Interface),一同在类型上又U h [ Z i h w能够到达比接口愈加细粒度的效果,一同它又像 JS 中的变量,能够一处修正,多处生效,防止硬编码类型带来 4 q b h 7 { 的一些代码上的重构和改动难题。

在进行类的类型注解之前,咱们首先先Y Y S * f w , 7 @来了解一下类的组成:

  • 结构函数
  • 特点
  • 实例特点
  • 静态特点
  • 办法
  • 实例办法
  • 静态办法

这是 ES6 里边类的一个组成,那么在 T] Z [ ^ ( ES 里边咱们该怎样注解这些内容了?主要有如下组成:

  • 注解结构函数
  • 注解特点:
  • 拜访限定符: public/protecH = 3 D zted/private
  • 润饰符:readonly
  • 注解办法
  • 拜访限定符:public/protected/privE 5 r 4 . , k hate

简略注解

了解e l g _ / B了类大致需求进行类型注r 3 w l J L l J解的部分,咱们来详细体会一下这个注解过程。

首先咱们来看一个动物类:

class Animal {
name;
static isAnimal(a) {
retB G ^urn a instanceof Animal;
}
constructor(name) {
this.name = name;
}` W / ,
move(distance) {
console.log(`Animal moved ${distance}m.`);
}
}

咱们能够看到上面这个类的四个部分:

  • 实例特点 name ,它一般是 string 类型,静态特点注解同实例特点f W n N相似
  • 静态办法 isAnimal ,依照之前解说的注解的函数办法进行注解:1)注解参数 2)注解回来值
  • 结构函数,注解参数
  • 一般办法,依照之前解说的注解的函数办法进行注解:1)注解y ^ &参数 2)注解回来值

了解之后,咱们来注解一下上面这个类:

class Animal {
name: st| f  &ring;
stat, 5 Jic isAne  B [ $ o mimal(a: AU [ | } P ^ Gnimal): boolean {
return a instanceof Animal;
}
constructor(name: string) {
this.name = namS W 7 x & `e;
}
move(distan( p K 7 o qce: number) {
console.log(`Animal moved ${distance}m.`);
}
}

能够看到,经过注解后的类看起来也很x S p ` z : L u z熟悉,由于都是之前学过的,这儿有_ e * C .个仅有的不同便是咱们的静态办法 isAnimal ,它接纳的参数 aAnimal 类本身来注解的,这儿就触及到两个常识:

  • 类能够拿来进行类型注解

  • 类的实例都能够用类名来注解

这两个常识咱们将在后面解说结构函数时详细解说。

拜访限定符

除了简略注解,TS 还给类赋予了一些共同的内容,其间一个便是大多数静态言语都有的拜访限定符:publicprotectedprivatU s 1e ,这些内容读者可能看起来很陌生了,咱们接下来就来细心讲一讲。

Public

public 代表公共的,表明被此拜访限定符润饰的特点,办法能A # & d 2 Q 5够任何当地拜访到:1)类中 2)类的实例目标 3)类的子类中 4)子类的实例目标 等,默许一切类的特点和办法都是 public 润饰的9 G t ` $ Q (,比方咱们拿上面那个 Animal 类来举例:

class` p v H ? i $ Animal {
public name: string;
// ...
public constructor(name: string) { // 函数体 }
// ...
}

能够看到其实咱们T y :name 特点和结构函数等,他们默许都是 public 拜访限定符,这样咱们能够在任0 [ [ V何当地拜访到这些特点,下面咱们就来看看怎样拜访这些特点。

在类内部拜访:

class Animal {
public name: string;
public c, i q e *onstructor(nay e ? I b K 5 qme: string) { // 函数体 }
move(distance: number) {
console.log(`${this.name} moved ${distance}m.`);
}
}
const bird = new Animal('Tuture');
bird.move(520); // 打印 `Tuture& 1 { 6 c moved 520m.`

能够看到,咱们在类内部的 move 办法内拜访了 publicv D i 类型的 name 特点。

在类外部拜访:

const animal = new Animal('bird');
console.log(animal.name) // 打印 bird

Q = H e Z % * k 7够看到,上面咱们经过类 Animal 的实例 animal 拜访到了 name 特点。

在子类中拜访:

class Bird exten? { .  % } 3ds Animal {
fly() {
console.log(`${this.name} can fly!`);
}
}
const bird = new Bird('Tuture');
bird.fly() // 打印 `Tuture can fly!`

能够看到,上面咱们在类 Animal 的子类 Bird 内部的 fly 办法拜访到了 name 特点。

在子类外部拜访:

class Bird extends Animal {
fly() {
console.log(`${this.namg 9 z H _ qe} can fly!`);
}
}
const bird = new Bird('Tut6 T kure');
consoT 5 j f n gle.log(bird.name) // 打印 Tuture

能够看到,上面咱们在子类 Bird 的实例 bird 上面拜访到了 name 特点。

Protected

接下来咱们来看一下第二个拜访限定符 protected ,它的字面意思是 “受维护的”,比 public 的可拜访的范围要小一些,它只能在类和子类中拜访,不能被类的实例目标拜访也不能被子类的实例目标拜访,也便是上面 public 的三种拜访里边,被 protected 拜访限定符润饰的只能在第一类和第三类里边被拜访到:

在类中拜访:

class Animal {
protN & H k f ~ `ected name: string;
public constructor(name: string) { // 函数体 }
move(distance: number) {
console.t % u $ i  y 0 :log(`${this.name} moK @ k o [ved ${distance}m.`);
}
}
const bird = new AnimT v k A ! F 1 o Oal('Tuture');
bird.move(520); // 打印 `Tuture moved 520m.`

能够看到,咱们在类内部的 move 办法内y N – 4 H拜访了 public 类型的 name 特点。

在子类中拜访:

class Animal {
protected name: string;
constructor(name: string) {
this.naZ l E $ ~ ] Ame = name
}
}
class Bird extends Animal {
fly() {
console.log(`${this.name} can fly!`);
}
}
const bird = neK ^ Tw Bird('Tuture');
bird.fly() // 打印 `Tuture can fo , z oly!`G  x P K .

能够看到,上面咱们在类 Animal 的子类 Bird 内部的 fly 办法拜访到了 naw ~ ^ , 3me 特点。

Private

第三类拜访限定符是 private ,它的字面意思是 “私有的”,也便是说它的能够拜访拜访是最小的,只能在类的内部拜访到,其他当地都无法拜访:

X 8 ^类中拜访:

class Animal {
private name: string;
public constructor(name: string) { /g S I X f M/ 函数体 }
move(distance: number) {
console.log() w b : !`${this.name} moved ${distance}m.`);
}
}
const bR W B 7 9 | cird = ney Y E 3 Dw Animal('Tuture');
bird.move(520); // 打印 `Tuture moved 520m.`

能够看到,咱们在类内部的 moE ] q = : :ve 办法内拜访了 public 类型的 name 特点。

只读润饰符

就像咱们之前学习的接口(Interface )时能够用 readonly 润饰接口的n s _ ? 3 h g s F特点相同,咱们也能够用 readonly 润饰类的特点,比方咱们动物的简介一旦确定就不会变了,咱们能a K k A T够这样来写:

clay 6 u q m Q Z Xss Animal {
readonly b+ g 8 P n U Arief: string = '动物是多细胞真核生命体中的一大类群,可是不同于微生物。';
// ...其他相同
}p v 3 5 ` 1 X

除了特点,咱们还能够用 readonly 来润饰类中# p = 1 { h m I办法的参数,比方咱们在设置此动物的类型时,一T E = 7 i { %般能够给一个默许的类型:

class Animal {
type: string;
setType(type: string, re[ x h 1 i /adonly defaultType = '哺乳动物') {
this.type = type || defaultType;
}
}

笼统类

笼统类与笼统办法

TS 别的一个特性便是笼统类,v v p M p [ V g o什么是笼统类了?咱们来看个比方:

abstract class Animal {
abstract makeSounj { e x $ i jd(): void;
move(): voidU h s ? * t {
console.log("Roaming the earth..l  d J p i P .");
}
}

能够看到笼统类便是在类之前加上 abstract 关键字,一同,它还不允许被实例化,也便是说如P a 9 U下的操作是不允许的:

const bird = new Animal() // Error

除此之外,笼统类比w F 3 L较一般类还有一个额定的特性便是,能够在笼] @ # D L k ~统类中界说笼统办法,就像咱们上面的 makeSound 办法,在一般的办法界说之N S E z f E %前加上 abstract# 5键字,这个笼统办法相似于接口里边的办法的类型界说:1)注解参数和回来值 2)不给出详细的完成,如上面的 move 便是存在详细的完成,而 makeSoun4 o rd 不给出@ z a J * g 详细的完成。

笼统8 W o 0 ] U类的承继

笼统类只能够被承继,不能够被实例化,且笼统类的承继I [ _ 6 e与一般类也存在不U g f { * L r G O同,一般类的承继能够只是简略的承继,并不需求额定的操作:

class Animal {
// Ane X cimal 相关的特点
}
class Bird extends Animal {
//r ^ d M 6 ] 不需求做任何操作
}

可是假如一个类承继别的一个笼统类,那么它必须得完成笼统类中的笼统办法:

abstract class Animal {
abstract makeSound(): void;t J k I 4 8 5 s 2
move(): void {
console.log("Roaming the earth...");
}
}
class Bird extends Animal {
makeSound(): void {
console.log('Tuture tuture tuture.');
}
}

能够M w U A d $ 看到,上面咱们界说了一个 Bird 类,它承继自 Animal 笼统类,它必须得完成 makeSound 笼统办法。

结构函数

经过上面的解说咱们根本了Z ] O o E , } ;解了 TS 中的类比较 JS 额Q ~ & w定添加的特性,主要是解说了怎样注解类的相关部分内容,接下来咱们侧重来谈一谈怎样用类来5 B d g n注解其他内容。这儿为什么类能够作为类型来注解z 5 Z C ! M Q其他内容了?本来在 TS 中声明[ + p a _ r :一个类的一同会创立多个声明:

1n U @ W)第一个声明是一个类型,这个类型是这个类实例目标类型,用于注解类的实例目标。

2)第二个声明则是类的结构函数,咱们在实例化类时,便是经过 new 关键字加上这个结构函数调用来生成一个类@ D m { 的实例。

声明注解类实例的类型

可能上面的概念听得有点懵,咱们拿之前那个比方来实际演示一下。

class Animal {
name: string;
static isAnimal(a: Animal): boolean {
return a instanceofA f g Animal;
}
constructor(name: string) {
this.name = name;
}
move(distance: number) {
c4 g N ( # v } monsole.log(`Animal moved ${distance}m.`);
}
}
const bird: Animal = new Animal('Tuture');

这第一个声明的用于注解类实例目标的类型便是咱们上面的 Animal ,当咱们声明晰一个 AW 5 8nimal 类之后,咱们能够用这个 Animal 来注解 Animal 的实例如 bird 或许 isAnimal 办法中的 a 参数,当你理解了这个概念之后,你b i K y会发现 isAnimal 办法只允许传入为 Animal 实例的参数 a ,然后回来一个 a instance Animal 的布尔值,这是一个永久a ^ n & 9 f Y o u回来 true 的函数。

提示

这儿这个声明的 AniH 4 L W ~ o i y (mal 类型不包括结构函数 constructorB ? G v Q ~ –及类中的静态办法和静态特点,就像实例目标中是不包括m { 6 c B s H ?类的结构函数、静态办法和静态特点相同。

声明结构函数

了解了第一个声明,那么第二个声明又是什么意思了?其实便是上面咱们履行 new Animaq U T A x C Yl('Tuture') 来生成一个实例时,这儿的 Animal 实际上便是一个结构函r A w } x数,经过 new Animal('Tuture') 调用实际上便是调用咱们类里边的 constructor 函数。

那么有的同学看到这儿就有疑问了,咱们的 AnimalH k ! 9 R 类型是用来注解类的实例的,那么类的结构函数 Animal 该怎样注解了?咱们来看这样一个比方:

let AnimalCreator = Animal;

在这段代码中,咱们将 Animal 结构函数赋值给 AnimalCre9 8 V W H 7 { w :ator ,那么咱们怎样注解这个 AnimalCreator 变量的类型了?当然 TS 具有] } k % V ] T –主动类型推导机制,; k K M一般情况下咱们是不需求注解这个变量的,但这儿假如咱们要注解它,那么该怎样M D ( { , % j – R注解了?答案是能够凭借 JS 原有的 typeof 办法:

let! ; v T N A 3 j AnimalCreator: typeof Animal = Animal;

咱们经过 typ2 h h D a [ |eof Animal 获取结构函数 Animal 的类型,然b i ~ $ $ g D k x后用此类型注解 AnimalCreator

类与接口

上面咱们了解了类在声明的时分会声明一个类型,此类型能够用于注解类的实例,其实这个类型和咱们之前学习的接口(Interface )有异曲同工之妙,详细类与接口结合运用的时分有如下U m m X K 0场景:

  • 类完成接口

  • 接口承继类

  • 类作为接口运用

类完成接口

类一般只能承继类,可是多个不同的类假如共有一些特点或A x h 8 4 y许办法时,就能够用接口来界说这1 Z G J B | q些特点或许办法,然后多个类来承继这个接口,以到达特点和p J H办法复用的意图,比方咱们有两个类 Door (门)和 Car (车),他们W ~ } v & #都有 Alarm (报警器)的功用,可是他们又是不同的类,这个时分咱们就能够界说一个 Alarm 接口:

in* I pterface Alarm {
alert(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alarm');
}
}
class Door impleC 8 - z F  c 6ments Alarm {
alert() {
console.l7 , m } l 5 O %og('Door a$ m Z 8 [ ? o - 4larm');
}
}

此刻的接口 Alarl p @ b } k X H ]m 和咱们之2 % :前界说的笼统类相似,接口中X / } @ t的办法 alert 相似笼统类中的笼统办法,一旦类完成 (implem- C } 5 4ents )了这个接口,那么也要完成这个接口中的办法,比方这儿的 alert

和类的单承继不相同,一个类能够完Q # R S A成多个接d E .口,比方咱们的车还能够开灯,那么咱们能够界说一个 Light 接口,给车整上W z r P K `灯:

interface Alarm {
alert(): void;
}
interface Light {
l$ h l t % 6 O 0ightOn(): void;
lightOff(): void;
}
cl) s sass Car implements Alarm, Li[ Y z yght {
alert() {
console.l{ o M  og('Car alarm');
}
lightOn() {
console.log('Car lighton');
}
la 6 X T # u Q } _i& d 8 V ! $ ^ghtOff() {
console.log('Car lightoff');
}
}

接口承继类

接口之所以能够承继类= + u ^ u p是由于咱们之前说到了类在声明的时分会声明一个类型,此类型用于注解类的实例。而接口承继类便是承继这个} ! U a | g | / |声明的类型,咱们来看一个比方:

class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: nuV F G | ?mber;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };

能够看到,接口 Point3d 承继自类 Point ,获取了来自类u l – + sxy 特点,实际上接口承继的是声明 Point 类时一同声明的用于注解类实例的那个类型,而这个类型只包括类的实例特点和办法,所以接口承继W 8 J M + Q ~ 1 P类也是承继此类的实例特点和办法的类型( N & 5 7 . 4 u r

类作为接口运用

类作为接口运用的场景主要在咱们给 React 组件的 PropsState 进行类型注解的时分,咱们既要给组件的 Props 进行类型注解,有时分还要设置组件的 defaultProps 值,这儿的 Props 的注解和 defaultProps 值设置本来需求分隔进行,咱们来看一个) d [ I 3 @比方:

interface TodoInputProps {
value: string;
ok q  anChange: (value: string) => void;
}
inteF { _ 4rface TodoInputState {
content: string;
user: string;
date: string;
}
const hardCodeDefaultProps = {
value: 'tuture',
onChange(value: string)? q L k { console.log(`Hello ${value}`); }
}
class TodoInput ext& [ U % _ end! q 0 a e 7 # Q ys React.Component<TodoInputProps, TodoInputState&E  I B !gt; {
static defat 6 0 Z k W XultProps: TodoInputProps = hardCodeDefaultProps;
render() {
return <div&gV ^ H 6t;Hello World</div>;
}
}

能够看到,上面是一个标准的 React 类组件,咱们经过 React.Component<TodoU A L v R ` N sID r 8 ~ n YnputProps, TodoInputState> 的办法注解了这个类组件的 PropsSY } {tate ,经过声明晰两个接口来进行注解,这儿z N K ` C React.Component<Todon S Y BInputProps, TodB 3 ) | 2 1 t r MoInputState> 便是泛型,现在不懂没关系,咱们将鄙人一节解说泛型,这儿能够理解泛型相似 JS 函数,这儿的 <> 相似函数的u o O T O + () ,然后能够接纳参数,这儿咱们传入了两个参数别离注解类的 PropsState

咱们还注意z @ H i到,咱们声明晰这个类的 defaultProps ,然后界4 O K说了一个 hardCodeDefaultProps 来初始化这个 defaultProps

这便是常见的 React 类组件的类型注解和默许参数初始化的场景,可是当咱们学了类之后,咱们能够简化一下上面的类组件A T 0 E t $ ; 9 9的类型注解和默许参数初始化的操作:

class TodoInputProps {
value: strinF v = + A bg = 'tuture';
onChange(value: string) {
console.log('Hello Tuture')i @ ! n Q ( :;
}
}, I K N
interface Todou ; pInputState {
content: string;
user: string;
date: string;
}
class TodoInput extends React.Component<TodoInputProps, TodoInpui } + U c  JtState> {
static defaultProps: TodoInputProps = new Props();
render() {
return &l/ # ^ 7 o ( ` ? 2t;div>Hello World</div>;
}
}

能够看到,上面咱们将接口 Props 换成了类 TodoInputPrG U ) Sops ,这带来了一些改变,便是类里边能够给出特点和办法的详细完成,而咱们又知道声明类 TodoInputProps 的时分会一同声明一个类型 TodoInputProps ,咱们用这个类型来注解组件的 Props ,然后注解 defaulP M p 3 x ]tProps ,然后咱们用声明类时声明的第3 4 w T T G x R g二个内容:TodoInputB z Z K V Z / Props 结构函数来创立一个 TodoII y 7 p e a q GnputProps 类型的实例目A * V $标并赋值给 defaultProps: r * ,细心的同学能够把这段代码复制到咱们之p D x _ |前的o c T y src/TodoInput.tI k * q , ysx 文件里,编辑器应该会显现正常,咱们成功利用了类的特性来帮助咱们的b P g G D S B K React 组件简化代码,进步了代码的逻辑性。+ k C

着手实践

学习了类的内容之后,咱们马大将学到的常识运用在咱们的待办事项小运用里边,打开 src/TodoInput.tsx ,对其间的内容作出对应的修正如下:

import React from "react";
import { Input, Seh : F ilect, DatePicker } from "antd";
import { Moment } from "moment";
// ...
interfaceN c , [ & q g L P TodoT C d ^ A $ A = FInF ) & 6 Kputg h  F D @ S t nProps {
value?: TodoValue;
onChange?: (value: TodoValue) => void;
}
interface Tod* c Q 2 C AoInputState {
content: string` n B Q } w 9 4 u;
user: UserId;
date: string;
}
class TodoInp# 9  9 # 3ut extends React.Component<TodoInputProps, TodoInputState> {
state = {
cono d f @ A F + te_ X : r 7nt: "",
useT ? @ # Ar: UserId.tuture,
date: ""
};
private triggerChn A G G ~ N = W Yange = (chW w m ( E K l 7angedValue: TI - R b = ; w jodoValue) => {
const { content, user, date } = this.state;
const { value, onChange } = this.props;
if (onChange) {
onChange({ content, user, date, ...value, .6 g K n..changedValue });
}
};
priN y m O | avate onContentChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value = {} } = th8 Q : ( w c g r $is.props;
if (!("content" in value!)) {
console.log("hello");
this.setState({
content: e.target.value
});
}
this.triggerChange({ content: e.target.value });
};
private onUserChange = (selectValue: UserId) => {
const { value = {} } = this.props;
if (!("user" in value!)) {
this.setState({
user: selectValue
});
}
this.triggerChange({ user: selectValue });
};
private onDateOk = (date: Moment) => {
const { value = {} } = this.props;
if (!("date" in value!)) {
thi( j t 8 2 z 4 e &s.setState({
date: date.format("YYYY-MM-DDS ) _ . HH:mm")
});
}
this.trl { v , T ` giggerChange({ date: date.format("YYYY-MM-DD HH:mm") });
};
public render() {
const { value } = this.props;
const { content, user } = this.state;
return (
<div className="todoInn Y T X B H 4 ( mput">
<Input
t9 x @ Bype="text"
placeholder="输入待办事项内容"
value={value?.contY Q A v [ent || content}
onChange={this.onContentChange}
/>
<Select
style={{ width: 80 }}
size="small"
defaultValue={UserId.tuture}
valuey - T G ~={value?.user || user}
onChange={this.onUserChange}
>
{userList.map(user => (
<OP | 0 s ( k ~ption valR Y ] 6 5 g 2 m Fue={user.id}>{x Q A : o ) t c =ux ( [ w L S ; 1ser.name}</Option>
))}
</SelecJ R m & J ^ xt>
<DatePicker
showTime
s % 7ize="sU G ^mall"
onOk={this.onDat5 ( w [ Y + ~ r )eOk}
style={{ marginLeft: "16px", marginRi^ y v $ Oght: "16px" }}
/>
</div>
);
}
}
export default TodoInput;

能够看到上面的改动主要有如下几处:

  • 咱们将之前的函数式组件改成了类组件,然后界说了一个 TodoInputState 接口,加上之前的 TodoInputProps ,一同以泛型的办法注解类的 PropsStL q T M 0atL Y Z Je ,接着咱们在类中加上实例特点 state
  • 接着咱们将 triggerChango k ]ek m | / & # : v ionCoy C ~ 1 e w w d fntentChangeonUserChangeJ 8 nonDateOk 四个办法改成了类的私有办法。
  • 最终咱们加上了类组件独有B } Q ! T W { Erender 办法,它是一个 public 类型的办法。

提示

这儿咱C | 7们在改造 onContC 7 { y DentChange 的时分,用 React.ChangeEve: x * X j B ! N 9nt<HTMLInputElemeQ c p knt> 的办法注解了办法参数的 e ,这儿也是泛型的一部分,咱们将鄙人一节侧重解说,% _ s ^ `这儿能够理解为一个 HTMLInputEQ ! E i } 0 ^lement类型的的 React.ChangeEventO _ : H g | 2 q -

那么有同学会% 2 c $ 2 k J有疑问了,这儿咱们是怎样知道该这样注解了?实际上,咱们看到4 I B j r} R X 6ender 办法里的 Input 组件的 onChange 办法,当咱a e 5 $ @ U们把鼠标放上去的时分,编辑器会给出如下提示:

能够看到,编辑器直接提示咱c p V t @ Y ! l们该怎样% 0 A 2注解 event 参数a i H ~ X E了,公然优秀的编辑器能够进步o ~ 5 R A !生产力啊!

小结

在这一节中,咱们学习了 TS 的类,主要学习了如下常识:

  • 了解一个类有哪些组成部分,以及怎样注解这些组成部分
  • 了解了 TS 类独有的拜访限定符:publicprotectedprivate
  • 了解了 TS 类就像接口相同,它的特点或许办法的参数也能够用 readonly 来润饰
  • 学习了 TS 的笼统类,知道了笼统类的笼统办法以及笼统类不能够直接被实例化,只能够被子类承继,且承继自笼统类的子类需求完成笼统类的笼统办法,即给出详z % M q 4 %细的同名办法的办法体
  • 接着,咱们学习了 TS 类的共同性,一同声明晰两个内容 1)一个用于注解类实例的类S * b n型 2)一个用于生成类实例的结构办法
  • 最终,咱们学习了类和接口的一些相互操作的场景 1)多个类完成同一个接口来复用接口的特点或许办法 2)一个类完成多– e : @ w 8 A h个接口 3)接口也能够承继类,只不过是承继类声明时一同4 K T G B U 3 d |声明的同名类型 4)类作为接口运用,经过进一步运用类声明的两个内容来简化 React 组件代码,进步代码的逻辑性和可复用性。

在这一节最终,咱们稍微引申了一下泛型,说它相似 JS 里边的函数,能够接纳类] X y 6 w型参数,鄙人一节中,咱们将重点解说泛型的g / q i 3 C .常识和运用,敬请期待!

想要学习更多精彩的实战技能教程?来图雀社区逛逛吧l m –

本文所触及的源代码都放在了 Github 或许 Gitee 上,假如您觉得咱们写得还不错,希望您能给❤️这篇文章点赞Github 或 Gitee 库房加星❤️哦~