咱們研發開源了一款基於 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 倉庫加星❤️哦~