咱们研发开源了一款基于 Git 进行技术实战教程写做的工具,咱们图雀社区的全部教程都是用这款工具写做而成,欢迎 Star 哦css
若是你想快速了解如何使用,欢迎阅读咱们的 教程文档 哦前端
学习了注解函数,又了解了类型运算如联合类型和交叉类型,接下来咱们来了解一些 TS 中独有的类型别名,它相似 JS 变量,是类型变量,接着咱们还会学习 TS 中内容很是庞杂的内容之一:类,了解 TS 中类的独有特性,以及如何注解类,甚至用类去注解其余内容。react
欢迎阅读 类型即正义,TypeScript 从入门到实践系列:git
本文所涉及的源代码都放在了 Github 或者 Gitee 上,若是您以为咱们写得还不错,但愿您能给❤️这篇文章点赞+Github 或 Gitee仓库加星❤️哦~github
此教程属于 React 前端工程师学习路线的一部分,欢迎来 Star 一波,鼓励咱们继续创做出更好的教程,持续更新中~typescript
若是你偏心 码云,那么你能够运行以下命令获取这一步的代码,而后你能够跟着文章的内容将代码作出修改:npm
git clone -b part-three https://gitee.com/tuture/typescript-tea.git
cd typescript-tea && npm install && npm start
复制代码
若是你偏心 Github,那么你能够运行以下命令来获取初始代码:bash
git clone -b part-thre git@github.com:tuture-dev/typescript-tea.git
cd typescript-tea && npm install && npm start
复制代码
就像咱们为了在平时开发中更加灵活而建立变量或者干掉硬编码数据同样,TS 为咱们提供了类型别名,它容许你为类型建立一个名字,这个名字就是类型的别名,进而你能够在多处使用这个别名,而且有必要的时候,你能够更改别名的值(类型),以达到一次替换,多处应用的效果。前端工程师
咱们来看一个简单的类型别名的例子,假如咱们有一个获取一我的姓名的函数,它接收一个参数,这个参数有可能直接是要获取的姓名,它是一个 string
类型,也有多是一个另一个函数,须要调用它以获取姓名,它是一个函数类型,咱们来看一下这个例子:antd
function getName(n) {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
复制代码
若是咱们要给这个 n
进行类型注解,那么它应该同时是 string | () => string
,是 string
类型和 () => string
函数类型的联合类型,有过必定开发经验的同窗可能会发觉,这样写可能很影响原代码的可读性,并且这个 n
的类型可能会变化,由于咱们的函数可能扩展,因此若是咱们用一个类型别名把这个 n
的类型表示出来,那么就相似咱们用变量替代了硬编码,可扩展性就更强了,咱们立刻来尝试一下:
type NameParams = 'string' | () => 'string';
function getName(n: NameParams): string {
// ... 其它同样
}
复制代码
能够看到咱们用了一个 NameParams
类型别名,它保存着原联合类型,类型别名就是等号左边是 type
关键字加上别名变量,等号右边是带保存的类型,这个类型很广,它能够是字面量类型,基础类型,元组、函数、联合类型和交叉类型、甚至还能够是其余类型别名的组合。
因此对于上面的 NameParams
,咱们能够进一步拆解它为以下的样子:
type Name = string;
type NameResolver = () => string;
type NameParams = Name | NameResolver;
function getName(n: NameParams): Name {
// ... 其余同样
}
复制代码
咱们看到,上面这个不只更加细粒度,咱们将 NameParams
拆成了两个类型别名:Name
和 NameResolver
,分别处理 string
和 () => string
的状况,而后经过联合操做符联合赋值给 NameParams
;还带来了一个优点,咱们的返回值能够更加明确就是 Name
类型,这样 Name
变化,它可能变成 number
类型,那么也能很好的反应这个变化,且只须要修改一下 Name
的值为 number
类型就能够了,全部其余的 Name
类型会自动变化。
有同窗读到这里,可能有疑问了,这个类型别名貌似无所不能嘛,那它和接口有什么区别了?
接口主要是用来定义一个结构的类型,好比定义一个对象的类型,而类型别名能够是任意细粒度的类型定义,好比咱们前面讲的最原子的字母量类型如 'hello tuture'
类型,到对象类型如:
type tuture = {
tutureCommunity: string;
editure: string;
tutureDocs: string;
}
复制代码
上面这个类型咱们定义了一个包含三个属性的对象类型,并用 tuture
别名来存储它们。
定义上面这个对象的类型咱们能够用以前学到的接口这样写:
interface Tuture {
tutureCommunity: string;
editure: string;
tutureDocs: string;
}
复制代码
能够看到类型别名既能够表达接口所表达的类型,还比接口更加细粒度,它还能够是一个基础类型如 type name = 'string'
。
还记得以前咱们那个 src/TodoList.tsx
中 Action
组件的 onClick
方法的参数 key
嘛?它是一个联合类型类型 "complete | delete"
,咱们在多出处用到它,如今咱们是硬编码写在了程序里,将来这个 key
可能会变化,因此咱们须要换成类型别名来表达它们,打开 src/TodoList.tsx
,对其中的内容做出对应的修改以下:
import React from "react";
import { List, Avatar, Menu, Dropdown } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { ClickParam } from "antd/lib/menu";
import { Todo, getUserById } from "./utils/data";
type MenuKey = "complete" | "delete";
interface ActionProps {
onClick: (key: MenuKey) => void;
isCompleted: boolean;
}
// ...
interface TodoListProps {
todoList: Todo[];
onClick: (todoId: string, key: MenuKey) => void;
}
function TodoList({ todoList, onClick }: TodoListProps) {
return (
<List
className="demo-loadmore-list"
itemLayout="horizontal"
dataSource={todoList}
renderItem={item => {
const user = getUserById(item.user);
return (
<List.Item
key={item.id}
actions={[
<Dropdown
overlay={() => (
<Action
isCompleted={item.isCompleted}
onClick={(key: MenuKey) => onClick(item.id, key)}
/>
)}
>
<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.date}
/>
<div
style={{
textDecoration: item.isCompleted ? "line-through" : "none"
}}
>
{item.content}
</div>
</List.Item>
);
}}
/>
);
}
export default TodoList;
复制代码
能够看到,咱们定义了一个 MenuKey
类型别名,它表示原联合类型 complete | delete
,而后咱们替换了组件中三处使用到这个联合类型的 onClick
函数的参数 key
,将其用 MenuKey
来注解。
其次咱们还删除了 antd
和 @ant-design/icons
里面的多余导出。
接着咱们再来对 TodoList
作一点改变,导出一下咱们刚刚定义的 MenuKey
,由于还有其余的地方使用到它,咱们打开 src/TodoList.tsx
给 MenuKey
添加 export
前缀,导出咱们的类型别名:
// ...
import { Todo, getUserById } from "./utils/data";
export type MenuKey = "complete" | "delete";
interface ActionProps {
onClick: (key: MenuKey) => void;
isCompleted: boolean;
}
// ...
复制代码
接着咱们在 src/App.tsx
里面导入咱们的 MenuKey
类型别名,并替换对应的 onClick
的参数 key
的类型注解为 MenuKey
:
import React, { useRef, useState } from "react";
import { Button, Typography, Form, Tabs } from "antd";
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
import { todoListData } from "./utils/data";
import { MenuKey } from "./TodoList";
import "./App.css";
import logo from "./logo.svg";
// ...
function App() {
const [todoList, setTodoList] = useState(todoListData);
// ...
const activeTodoList = todoList.filter(todo => !todo.isCompleted);
const completedTodoList = todoList.filter(todo => todo.isCompleted);
const onClick = (todoId: string, key: MenuKey) => {
if (key === "complete") {
const newTodoList = todoList.map(todo => {
if (todo.id === todoId) {
return { ...todo, isCompleted: !todo.isCompleted };
}
return todo;
});
setTodoList(newTodoList);
} else if (key === "delete") {
const newTodoList = todoList.filter(todo => todo.id !== todoId);
setTodoList(newTodoList);
}
};
// ...
return (
<div className="App" ref={ref}> // ... </div>
);
}
export default App;
复制代码
能够看到如上文件里面,咱们还删除了一些 antd
里面没必要要的包导入。
这一节咱们学习了类型别名,它能够在必定程度上模拟接口(Interface),同时在类型上又能够达到比接口更加细粒度的效果,同时它又像 JS 中的变量,能够一处修改,多处生效,避免硬编码类型带来的一些代码上的重构和改动难题。
在进行类的类型注解以前,咱们首先先来了解一下类的组成:
这是 ES6 里面类的一个组成,那么在 TS 里面咱们该如何注解这些内容了?主要有以下组成:
public/protected/private
readonly
public/protected/private
了解了类大体须要进行类型注解的部分,咱们来具体体验一下这个注解过程。
首先咱们来看一个动物类:
class Animal {
name;
static isAnimal(a) {
return a instanceof Animal;
}
constructor(name) {
this.name = name;
}
move(distance) {
console.log(`Animal moved ${distance}m.`);
}
}
复制代码
咱们能够看到上面这个类的四个部分:
name
,它通常是 string
类型,静态属性注解同实例属性相似isAnimal
,按照以前讲解的注解的函数方式进行注解:1)注解参数 2)注解返回值了解以后,咱们来注解一下上面这个类:
class Animal {
name: string;
static isAnimal(a: Animal): boolean {
return a instanceof Animal;
}
constructor(name: string) {
this.name = name;
}
move(distance: number) {
console.log(`Animal moved ${distance}m.`);
}
}
复制代码
能够看到,通过注解后的类看起来也很熟悉,由于都是以前学过的,这里有个惟一的不一样就是咱们的静态方法 isAnimal
,它接收的参数 a
是 Animal
类自己来注解的,这里就涉及到两个知识:
类能够拿来进行类型注解
类的实例均可以用类名来注解
这两个知识咱们将在后面讲解构造函数时详细讲解。
除了简单注解,TS 还给类赋予了一些独特的内容,其中一个就是大多数静态语言都有的访问限定符:public
、protected
和 private
,这些内容读者可能看起来很陌生了,咱们接下来就来仔细讲一讲。
public
表明公共的,表示被此访问限定符修饰的属性,方法能够任何地方访问到:1)类中 2)类的实例对象 3)类的子类中 4)子类的实例对象 等,默认全部类的属性和方法都是 public
修饰的,好比咱们拿上面那个 Animal
类来举例:
class Animal {
public name: string;
// ...
public constructor(name: string) { // 函数体 }
// ...
}
复制代码
能够看到其实咱们的 name
属性和构造函数等,他们默认都是 public
访问限定符,这样咱们能够在任何地方访问到这些属性,下面咱们就来看看如何访问这些属性。
在类内部访问:
class Animal {
public name: string;
public constructor(name: string) { // 函数体 }
move(distance: number) {
console.log(`${this.name} moved ${distance}m.`);
}
}
const bird = new Animal('Tuture');
bird.move(520); // 打印 `Tuture moved 520m.`
复制代码
能够看到,咱们在类内部的 move
方法内访问了 public 类型的 name
属性。
在类外部访问:
const animal = new Animal('bird');
console.log(animal.name) // 打印 bird
复制代码
能够看到,上面咱们经过类 Animal
的实例 animal
访问到了 name
属性。
在子类中访问:
class Bird extends 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.name} can fly!`);
}
}
const bird = new Bird('Tuture');
console.log(bird.name) // 打印 Tuture
复制代码
能够看到,上面咱们在子类 Bird
的实例 bird
上面访问到了 name
属性。
接下来咱们来看一下第二个访问限定符 protected
,它的字面意思是 “受保护的”,比 public
的可访问的范围要小一些,它只能在类和子类中访问,不能被类的实例对象访问也不能被子类的实例对象访问,也就是上面 public
的三种访问里面,被 protected
访问限定符修饰的只能在第一类和第三类里面被访问到:
在类中访问:
class Animal {
protected name: string;
public constructor(name: string) { // 函数体 }
move(distance: number) {
console.log(`${this.name} moved ${distance}m.`);
}
}
const bird = new Animal('Tuture');
bird.move(520); // 打印 `Tuture moved 520m.`
复制代码
能够看到,咱们在类内部的 move
方法内访问了 public 类型的 name
属性。
在子类中访问:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name
}
}
class Bird extends Animal {
fly() {
console.log(`${this.name} can fly!`);
}
}
const bird = new Bird('Tuture');
bird.fly() // 打印 `Tuture can fly!`
复制代码
能够看到,上面咱们在类 Animal
的子类 Bird
内部的 fly
方法访问到了 name
属性。
第三类访问限定符是 private
,它的字面意思是 “私有的”,也就是说它的能够访问访问是最小的,只能在类的内部访问到,其余地方都没法访问:
在类中访问:
class Animal {
private name: string;
public constructor(name: string) { // 函数体 }
move(distance: number) {
console.log(`${this.name} moved ${distance}m.`);
}
}
const bird = new Animal('Tuture');
bird.move(520); // 打印 `Tuture moved 520m.`
复制代码
能够看到,咱们在类内部的 move
方法内访问了 public 类型的 name
属性。
就像咱们以前学习的接口(Interface
)时能够用 readonly
修饰接口的属性同样,咱们也能够用 readonly
修饰类的属性,好比咱们动物的简介一旦肯定就不会变了,咱们能够这样来写:
class Animal {
readonly brief: string = '动物是多细胞真核生命体中的一大类群,可是不一样于微生物。';
// ...其余同样
}
复制代码
除了属性,咱们还能够用 readonly
来修饰类中方法的参数,好比咱们在设置此动物的类型时,通常能够给一个默认的类型:
class Animal {
type: string;
setType(type: string, readonly defaultType = '哺乳动物') {
this.type = type || defaultType;
}
}
复制代码
TS 另一个特性就是抽象类,什么是抽象类了?咱们来看个例子:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Roaming the earth...");
}
}
复制代码
能够看到抽象类就是在类以前加上 abstract
关键字,同时,它还不容许被实例化,也就是说以下的操做是不容许的:
const bird = new Animal() // Error
复制代码
除此以外,抽象类相比普通类还有一个额外的特性就是,能够在抽象类中定义抽象方法,就像咱们上面的 makeSound
方法,在普通的方法定义以前加上 abstract
关键字,这个抽象方法相似于接口里面的方法的类型定义:1)注解参数和返回值 2)不给出具体的实现,如上面的 move
就是存在具体的实现,而 makeSound
不给出具体的实现。
抽象类只能够被继承,不能够被实例化,且抽象类的继承与普通类也存在不一样,普通类的继承能够只是简单的继承,并不须要额外的操做:
class Animal {
// Animal 相关的属性
}
class Bird extends Animal {
// 不须要作任何操做
}
复制代码
可是若是一个类继承另一个抽象类,那么它必须得实现抽象类中的抽象方法:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Roaming the earth...");
}
}
class Bird extends Animal {
makeSound(): void {
console.log('Tuture tuture tuture.');
}
}
复制代码
能够看到,上面咱们定义了一个 Bird
类,它继承自 Animal
抽象类,它必须得实现 makeSound
抽象方法。
经过上面的讲解咱们基本了解了 TS 中的类相比 JS 额外增长的特性,主要是讲解了如何注解类的相关部份内容,接下来咱们着重来谈一谈如何用类来注解其余内容。这里为何类能够做为类型来注解其余内容了?原来在 TS 中声明一个类的同时会建立多个声明:
1)第一个声明是一个类型,这个类型是这个类实例对象类型,用于注解类的实例对象。
2)第二个声明则是类的构造函数,咱们在实例化类时,就是经过 new
关键字加上这个构造函数调用来生成一个类的实例。
可能上面的概念听得有点懵,咱们拿以前那个例子来实际演示一下。
class Animal {
name: string;
static isAnimal(a: Animal): boolean {
return a instanceof Animal;
}
constructor(name: string) {
this.name = name;
}
move(distance: number) {
console.log(`Animal moved ${distance}m.`);
}
}
const bird: Animal = new Animal('Tuture');
复制代码
这第一个声明的用于注解类实例对象的类型就是咱们上面的 Animal
,当咱们声明了一个 Animal
类以后,咱们能够用这个 Animal
来注解 Animal
的实例如 bird
或者 isAnimal
方法中的 a
参数,当你理解了这个概念以后,你会发现 isAnimal
方法只容许传入为 Animal
实例的参数 a
,而后返回一个 a instance Animal
的布尔值,这是一个永远返回 true
的函数。
提示
这里这个声明的
Animal
类型不包括构造函数constructor
以及类中的静态方法和静态属性,就像实例对象中是不包含类的构造函数、静态方法和静态属性同样。
了解了第一个声明,那么第二个声明又是什么意思了?其实就是上面咱们执行 new Animal('Tuture')
来生成一个实例时,这里的 Animal
实际上就是一个构造函数,经过 new Animal('Tuture')
调用实际上就是调用咱们类里面的 constructor
函数。
那么有的同窗看到这里就有疑问了,咱们的 Animal
类型是用来注解类的实例的,那么类的构造函数 Animal
该如何注解了?咱们来看这样一个例子:
let AnimalCreator = Animal;
复制代码
在这段代码中,咱们将 Animal
构造函数赋值给 AnimalCreator
,那么咱们如何注解这个 AnimalCreator
变量的类型了?固然 TS 具备自动类型推导机制,通常状况下咱们是不须要注解这个变量的,但这里若是咱们要注解它,那么该如何注解了?答案是能够借助 JS 原有的 typeof
方法:
let AnimalCreator: typeof Animal = Animal;
复制代码
咱们经过 typeof Animal
获取构造函数 Animal
的类型,而后用此类型注解 AnimalCreator
。
上面咱们了解了类在声明的时候会声明一个类型,此类型能够用于注解类的实例,其实这个类型和咱们以前学习的接口(Interface
)有殊途同归之妙,具体类与接口结合使用的时候有以下场景:
类实现接口
接口继承类
类做为接口使用
类通常只能继承类,可是多个不一样的类若是共有一些属性或者方法时,就能够用接口来定义这些属性或者方法,而后多个类来继承这个接口,以达到属性和方法复用的目的,好比咱们有两个类 Door
(门)和 Car
(车),他们都有 Alarm
(报警器)的功能,可是他们又是不一样的类,这个时候咱们就能够定义一个 Alarm
接口:
interface Alarm {
alert(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alarm');
}
}
class Door implements Alarm {
alert() {
console.log('Door alarm');
}
}
复制代码
此时的接口 Alarm
和咱们以前定义的抽象类相似,接口中的方法 alert
相似抽象类中的抽象方法,一旦类实现 (implements
)了这个接口,那么也要实现这个接口中的方法,好比这里的 alert
。
和类的单继承不同,一个类能够实现多个接口,好比咱们的车还能够开灯,那么咱们能够定义一个 Light
接口,给车整上灯:
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alarm');
}
lightOn() {
console.log('Car lighton');
}
lightOff() {
console.log('Car lightoff');
}
}
复制代码
接口之因此能够继承类是由于咱们以前说到了类在声明的时候会声明一个类型,此类型用于注解类的实例。而接口继承类就是继承这个声明的类型,咱们来看一个例子:
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };
复制代码
能够看到,接口 Point3d
继承自类 Point
,获取了来自类的 x
和 y
属性,实际上接口继承的是声明 Point
类时同时声明的用于注解类实例的那个类型,而这个类型只包含类的实例属性和方法,因此接口继承类也是继承此类的实例属性和方法的类型。
类做为接口使用的场景主要在咱们给 React 组件的 Props
和 State
进行类型注解的时候,咱们既要给组件的 Props
进行类型注解,有时候还要设置组件的 defaultProps
值,这里的 Props
的注解和 defaultProps
值设置本来须要分开进行,咱们来看一个例子:
interface TodoInputProps {
value: string;
onChange: (value: string) => void;
}
interface TodoInputState {
content: string;
user: string;
date: string;
}
const hardCodeDefaultProps = {
value: 'tuture',
onChange(value: string) { console.log(`Hello ${value}`); }
}
class TodoInput extends React.Component<TodoInputProps, TodoInputState> {
static defaultProps: TodoInputProps = hardCodeDefaultProps;
render() {
return <div>Hello World</div>;
}
}
复制代码
能够看到,上面是一个标准的 React 类组件,咱们经过 React.Component<TodoInputProps, TodoInputState>
的形式注解了这个类组件的 Props
和 State
,经过声明了两个接口来进行注解,这里 React.Component<TodoInputProps, TodoInputState>
就是泛型,如今不懂不要紧,咱们将在下一节讲解泛型,这里能够理解泛型相似 JS 函数,这里的 <>
相似函数的 ()
,而后能够接收参数,这里咱们传入了两个参数分别注解类的 Props
和 State
。
咱们还注意到,咱们声明了这个类的 defaultProps
,而后定义了一个 hardCodeDefaultProps
来初始化这个 defaultProps
。
这就是常见的 React 类组件的类型注解和默认参数初始化的场景,可是当咱们学了类以后,咱们能够简化一下上面的类组件的类型注解和默认参数初始化的操做:
class TodoInputProps {
value: string = 'tuture';
onChange(value: string) {
console.log('Hello Tuture');
}
}
interface TodoInputState {
content: string;
user: string;
date: string;
}
class TodoInput extends React.Component<TodoInputProps, TodoInputState> {
static defaultProps: TodoInputProps = new Props();
render() {
return <div>Hello World</div>;
}
}
复制代码
能够看到,上面咱们将接口 Props
换成了类 TodoInputProps
,这带来了一些改变,就是类里面能够给出属性和方法的具体实现,而咱们又知道声明类 TodoInputProps
的时候会同时声明一个类型 TodoInputProps
,咱们用这个类型来注解组件的 Props
,而后注解 defaultProps
,而后咱们用声明类时声明的第二个内容:TodoInputProps
构造函数来建立一个 TodoInputProps
类型的实例对象并赋值给 defaultProps
,细心的同窗能够把这段代码复制到咱们以前的 src/TodoInput.tsx
文件里,编辑器应该会显示正常,咱们成功利用了类的特性来帮助咱们的 React 组件简化代码,提升了代码的逻辑性。
学习了类的内容以后,咱们立刻将学到的知识运用在咱们的待办事项小应用里面,打开 src/TodoInput.tsx
,对其中的内容做出对应的修改以下:
import React from "react";
import { Input, Select, DatePicker } from "antd";
import { Moment } from "moment";
// ...
interface TodoInputProps {
value?: TodoValue;
onChange?: (value: TodoValue) => void;
}
interface TodoInputState {
content: string;
user: UserId;
date: string;
}
class TodoInput extends React.Component<TodoInputProps, TodoInputState> {
state = {
content: "",
user: UserId.tuture,
date: ""
};
private triggerChange = (changedValue: TodoValue) => {
const { content, user, date } = this.state;
const { value, onChange } = this.props;
if (onChange) {
onChange({ content, user, date, ...value, ...changedValue });
}
};
private onContentChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value = {} } = this.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!)) {
this.setState({
date: date.format("YYYY-MM-DD HH:mm")
});
}
this.triggerChange({ date: date.format("YYYY-MM-DD HH:mm") });
};
public render() {
const { value } = this.props;
const { content, user } = this.state;
return (
<div className="todoInput">
<Input
type="text"
placeholder="输入待办事项内容"
value={value?.content || content}
onChange={this.onContentChange}
/>
<Select
style={{ width: 80 }}
size="small"
defaultValue={UserId.tuture}
value={value?.user || user}
onChange={this.onUserChange}
>
{userList.map(user => (
<Option value={user.id}>{user.name}</Option>
))}
</Select>
<DatePicker
showTime
size="small"
onOk={this.onDateOk}
style={{ marginLeft: "16px", marginRight: "16px" }}
/>
</div>
);
}
}
export default TodoInput;
复制代码
能够看到上面的改动主要有以下几处:
TodoInputState
接口,加上以前的 TodoInputProps
,一块儿以泛型的形式注解类的 Props
和 State
,接着咱们在类中加上实例属性 state
。triggerChange
、onContentChange
、onUserChange
、onDateOk
四个方法改为了类的私有方法。render
方法,它是一个 public
类型的方法。提示
这里咱们在改造
onContentChange
的时候,用React.ChangeEvent<HTMLInputElement>
的方式注解了方法参数的e
,这里也是泛型的一部分,咱们将在下一节着重讲解,这里能够理解为一个HTMLInputElement
类型的的React.ChangeEvent
。那么有同窗会有疑问了,这里咱们是如何知道该这样注解了?实际上,咱们看到
render
方法里的Input
组件的onChange
方法,当咱们把鼠标放上去的时候,编辑器会给出以下提示:![]()
能够看到,编辑器直接提醒咱们该怎么注解
event
参数了,果真优秀的编辑器能够提升生产力啊!
在这一节中,咱们学习了 TS 的类,主要学习了以下知识:
public
、protected
和 private
readonly
来修饰在这一节最后,咱们稍微引伸了一下泛型,说它相似 JS 里面的函数,能够接收类型参数,在下一节中,咱们将重点讲解泛型的知识和应用,敬请期待!
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。
本文所涉及的源代码都放在了 Github 或者 Gitee 上,若是您以为咱们写得还不错,但愿您能给❤️这篇文章点赞Github 或 Gitee 仓库加星❤️哦~