做者:一只图雀
仓库: Github 、 Gitee
图雀社区主站(首发): 图雀社区
博客: 掘金、 知乎、 慕课
公众号: 图雀社区
联系我:关注公众号后能够加图雀酱微信哦
原创不易,❤️点赞+评论+收藏 ❤️三连,鼓励做者写出更好的教程。
欢迎阅读 类型即正义,TypeScript 从入门到实践系列:css
了解了基础的 TS 类型,接口以后,咱们开始了解如何给更加复杂的结构注解类型,这就是咱们这节里面要引出的函数,进而咱们讲解如何对类型进行运算:交叉类型和联合类型,最后咱们讲解了最原子类型:字面量类型,以及如何与联合类型搭配实现类型守卫效果。html
本文所涉及的源代码都放在了 Github 或者 Gitee 上,若是您以为咱们写得还不错,但愿您能给 ❤️这篇文章点赞+Github 或 Gitee仓库加星❤️哦~
此教程属于 React 前端工程师学习路线的一部分,欢迎来 Star 一波,鼓励咱们继续创做出更好的教程,持续更新中~
咱们在以前 TodoInputProps
中对 onChange
函数作了类型注解,当时咱们没有详细讲解,在这一节中咱们就来详细讲解一下 TS 中的函数。前端
好比咱们有以下的函数:react
function add(x, y) { return x + y; }
那么咱们该如何注解这个函数了?实际上函数主要的部分就是输入和输出,因此咱们在注解函数的时候只须要注解函数的参数和返回值就能够了,由于上述的函数体内是是执行 x+y
操做,以咱们的 x
和 y
应该都是 number
数字类型,返回值也是 number
数字类型,因此咱们对上面的函数进行类型注解以下:linux
function add(x: number, y: number): number { return x + y; }
能够看到咱们用冒号注解形式给 x
和 y
注解了 number
类型,而对于返回值,咱们直接以 add(): number
的形式注解返回值。有时候返回值也能够不写,TS 能够根据参数类型和函数体计算返回值类型,也就是俗称的自动推断类型机制。git
除了注解函数,有时候咱们还涉及到将函数赋值给一个变量,好比以下的例子:github
const add = function (x, y) { return x + y; }
这个时候咱们通常来注解 add
时候,就须要使用函数类型来注解它,一个函数类型是形如:(args1: type1, args2: type2, ..., args2: typen) => returnType
的类型,因此对于上述的例子咱们能够对其注解以下:typescript
const add: (x: number, y: number): number = function(x, y) { return x + y; }
可能有同窗有疑问了,这里咱们给 add
变量注解了函数类型,可是咱们没有给后面的那个函数进行一个注解啊?其实 TS 会进行类型的自动推导,根据函数类型的结构对比后面的函数,会自动推断出后面函数的 x
,y
和返回值都为 number
。segmentfault
就像咱们以前接口(Interface)中有可选属性同样,咱们的函数中也存在可选参数,由于使用 TS 最大的好处之一就是尽量的明确函数、接口等类型定义,方便其余团队成员很清晰了解代码的接口,大大提升团队协做的效率,因此若是一个函数可能存在一些参数,可是咱们并非每次都须要传递这些参数,那么它们就属于可选参数的范围。windows
咱们来看一下可选参数的例子,好比咱们想写一个构造一我的姓名的函数,包含 firstName
和 lastName
,可是有时候咱们不知道 lastName
,那么这样一个函数该怎么写了?:
function buildName(firstName: string, lastName?: string) { // ... }
能够看到上面咱们构建一我的姓名的函数,必须得传递 firstName
属性,可是由于 lastName
可能有时候并不能获取到,因此把它设置为可选参数,因此如下几种函数调用方式都是能够的:
buildName('Tom', 'Huang'); buildName('mRcfps');
重载(Overloads)是 TS 独有的概念,在 JS 中没有,它主要为函数多返回类型服务,具体来讲就是一个函数可能会在内部执行一个条件语句,根据不一样的条件返回不一样的值,这些值多是不一样类型的,那么这个时候咱们该怎么来给返回值注解类型了?
答案就是使用重载,经过定义一系列一样函数名,不一样参数列表和返回值的函数来注解多类型返回值函数,咱们来看一个多类型返回的函数:
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x): any { // 若是 x 是 `object` 类型,那么咱们返回 pickCard 从 myDeck 里面取出 pickCard1 数据 if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // 若是 x 是 `number` 类型,那么直接返回一个能够取数据的 pickCard2 else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [ { suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 } ]; let pickedCard1 = myDeck[pickCard(myDeck)]; alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
针对上面的这个例子,咱们这个 pickCard
函数根据 x
的类型会有不一样的返回类型,有的同窗可能会有疑问了,以前咱们不是说过,TS 可以根据参数类型和函数体自动推断返回值类型嘛?是的,以前那个例子参数类型只有一种选项,因此能够自动推断出返回值类型,可是这里的状况是:“参数类型可能有多种选项,对应不一样选项的参数类型,会有不一样的返回值类型,而且咱们对参数类型还未知”。针对这种状况,咱们直接解耦这个对应关系,使用重载就能够很好的表达出来:
let suits = ["hearts", "spades", "clubs", "diamonds"]; function pickCard(x: { suit: string; card: number }[]): number; function pickCard(x: number): { suit: string; card: number }; function pickCard(x): any { // 若是 x 是 `object` 类型,那么咱们返回 pickCard 从 myDeck 里面取出 pickCard1 数据 if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // 若是 x 是 `number` 类型,那么直接返回一个能够取数据的 pickCard2 else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [ { suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 } ]; let pickedCard1 = myDeck[pickCard(myDeck)]; alert("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
咱们能够看到这段代码比上面惟一多了的就是两端 function pickCard(x: type1): type2
语句,因此重载实际上就是函数名同样,参数列表和返回值不同,咱们来解析一下上面多出的两个重载:
x
赋值了一个数组类型,数组的项是一个对象,对象包含两个属性 suit
和 card
,它们的类型分别为 string
和 number
;接着返回值类型为 number
类型,这个对应 x
的类型为 object
时,返回类型为 number
这种状况。x
赋值了一个 number
类型,而后返回值类型是一个对象,它有两个属性 suit
和 card
,对应的类型为 string
和 number
;这个对应 x
的类型为 number
返回值类型为 object
类型这种状况。学习了 TS 的函数以后,咱们立刻来运用在咱们的 待办事项 应用里面,首先咱们打开 src/utils/data.ts
对其中的数据作一点修改:
export interface Todo { id: string; user: string; date: string; content: string; isCompleted: boolean; } export interface User { id: string; name: string; avatar: string; } export function getUserById(userId: string) { return userList.filter(user => user.id === userId)[0]; } export const todoListData: Todo[] = [ { id: "1", content: "图雀社区:汇聚精彩的免费实战教程", user: "23410977", date: "2020年3月2日 19:34", isCompleted: false }, { id: "2", content: "图雀社区:汇聚精彩的免费实战教程", user: "23410976", date: "2020年3月2日 19:34", isCompleted: false }, { id: "3", content: "图雀社区:汇聚精彩的免费实战教程", user: "58352313", date: "2020年3月2日 19:34", isCompleted: false }, { id: "4", content: "图雀社区:汇聚精彩的免费实战教程", user: "25455350", date: "2020年3月2日 19:34", isCompleted: false }, { id: "5", content: "图雀社区:汇聚精彩的免费实战教程", user: "12345678", date: "2020年3月2日 19:34", isCompleted: true } ]; export const userList: User[] = [ // ... { id: "23410976", name: "pftom", avatar: "https://avatars1.githubusercontent.com/u/26423749?s=88&v=4" }, // ... { id: "12345678", name: "pony", avatar: "https://avatars3.githubusercontent.com/u/25010151?s=96&v=4" } ];
能够看到,上面咱们主要作出了以下几处修改:
todoListData
的每一个元素的 user
字段改成对应 userList
元素的 id
,方便基于 user
的 id
进行用户信息的查找。todoListData
每一个元素添加了 id
方便标志,而后把 time
属性替换成了 date
属性。getUserById
函数,用于每一个 todo
中根据 user
字段来获取对应的用户详情,包括名字和头像等,这里咱们有些同窗可能有疑问了,咱们给参数作了类型注解,为啥不须要注解返回值了?其实这也是 TS 自动类型推断的一个应用场景,TS 编译器会根据参数的类型而后自动计算返回值类型,因此咱们就不须要明确的指定返回值啦。Todo
和 User
接口。接着咱们相似单首创建 src/TodoInput.tsx
组件给 src/App.tsx
减负同样,尝试建立 src/TodoList.tsx
组件,而后把对应 src/App.tsx
的对应逻辑移动到这个组件里:
import React from "react"; import { List, Avatar, Menu, Dropdown } from "antd"; import { DownOutlined } from "@ant-design/icons"; import { Todo, getUserById } from "./utils/data"; const menu = ( <Menu> <Menu.Item>完成</Menu.Item> <Menu.Item>删除</Menu.Item> </Menu> ); interface TodoListProps { todoList: Todo[]; } function TodoList({ todoList }: 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={menu}> <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;
能够看到,上面咱们主要作了以下改动:
Todo
接口,给 TodoList 组件增长了 TodoListProps
接口用于给这个组件的 props
作类型注解。getUserById
,用于在 renderItem
里面根据 item.user
获取用户详情信息,而后展现头像和姓名。item.time
更新为 item.date
line-through
的 textDecoration
属性,来标志已经完成的事项。最后咱们来根据上面的改进来修改对应的 src/App.tsx
:
import React, { useRef, useState } from "react"; import { List, Avatar, // ... Dropdown, Tabs } from "antd"; import TodoInput from "./TodoInput"; import TodoList from "./TodoList"; import { todoListData } from "./utils/data"; import "./App.css"; import logo from "./logo.svg"; const { Title } = Typography; const { TabPane } = Tabs; function App() { const [todoList, setTodoList] = useState(todoListData); const callback = () => {}; const onFinish = (values: any) => { const newTodo = { ...values.todo, isCompleted: false }; setTodoList(todoList.concat(newTodo)); }; const ref = useRef(null); const activeTodoList = todoList.filter(todo => !todo.isCompleted); const completedTodoList = todoList.filter(todo => todo.isCompleted); return ( <div className="App" ref={ref}> <div className="container header"> // ... <div className="container"> <Tabs onChange={callback} type="card"> <TabPane tab="全部" key="1"> <TodoList todoList={todoList} /> </TabPane> <TabPane tab="进行中" key="2"> <TodoList todoList={activeTodoList} /> </TabPane> <TabPane tab="已完成" key="3"> <TodoList todoList={completedTodoList} /> </TabPane> </Tabs> </div> // ...
能够看到上面的内容做出了以下的修改:
TodoList
部分代码,转而导入了 TodoList
组件useState
Hooks 接收 todoListData
做为默认数据,而后经过 isCompleted
过滤,生成咱们来总结和回顾一下这一小节学到的知识:
由于本篇文章是图雀社区一杯茶系列,因此关于函数的知识,咱们还有一些内容没有讲解到,不过具体内容都是举一反三,好比注解函数的 rest
参数,this
等,有兴趣的同窗能够查阅官方文档:TS-函数。
在前三个大章节中,咱们咱们讲解了基础的 TS 类型,而后接着咱们用这些学到的基础类型,去组合造成枚举和接口,去注解函数的参数和返回值,这都是 TS 类型注解到 JS 元素上的实践,那么就像 JS 中有元素运算同样如加减乘除甚至集合运算 “交并补”,TS 中也存在类型的一个运算,这就是咱们这一节中要讲解的交叉和联合类型。
交叉类型就是多个类型,经过 &
类型运算符,合并成一个类型,这个类型包含了多个类型中的全部类型成员,咱们来看个响应体的例子,假如咱们有一个查询艺术家的请求,咱们要根据查询的结果 -- 响应体,打印对应信息,通常响应体是两类信息的综合:
针对这一一个场景,咱们就可使用交叉类型,了解了这样一个场景以后,那么咱们再来看一下对应这个场景的具体例子:
interface ErrorHandling { success: boolean; error?: { message: string }; } interface ArtistsData { artists: { name: string }[]; } const handleArtistsResponse = (response: ArtistsData & ErrorHandling) => { if (response.error) { console.error(response.error.message); return; } console.log(response.artists); };
咱们能够看到这个例子,咱们的艺术家信息接口(Interface)是 ArtistsData
,它是请求成功以后返回的具体数据之一,除了这个,咱们的响应体通常还有标志响应是否成功的状态,以及错误的时候的打印信息,因此咱们还定义了一个 ErrorHandling
,它们两个进行一个交叉类型操做就组成了咱们的艺术家响应体:ArtistsData & ErrorHandling
,而后咱们在函数参数里面标志 response
为这个交叉类型的结果,并在函数体之类根据请求是否成功的状态 reponse.error
判断来打印对应的信息。
那么联合类型是什么了?联合类型其实是经过操做符 |
,将多个类型进行联合,组成一个复合类型,当用这个复合类型注解一个变量的时候,这个变量能够取这个复合类型中的任意一个类型,这个有点相似枚举了,就是一个变量可能存在多个类型,可是最终只能取一个类型。
读者这里能够自行了解联合类型和枚举类型的一个细节差别,本文首先于篇幅,不具体展开。
接下来咱们来看个联合类型应用的场景,好比咱们有一个 padLeft
函数 -- 左填充空格操做,它负责接收两个参数 value
和 padding
,主要目标是实现给 value
这个字符串左边添加 padding
,能够类比这个 padding
就是空格,可是这里的 padding
既能够是字符串 string
类型,也能够是数字 number
,当 padding
是字符串时,一个比较简单的例子以下:
const value: string = 'Hello Tuture'; const padding: string = ' '; padLeft(value, padding) // => ' Hello Tuture';
好的,了解的场景以后,咱们立刻来一个实战,讲解上面那个例子的一个升级版:
function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4);
能够看到这个例子,padding
咱们暂时给了 any
,而后函数体里面对 string
和 number
类型给了判断,执行对应的 “左空格填充” 操做,这个逻辑对于研发初期是可行的,可是当咱们涉及到多人协做开发的时候,其余成员光看这个函数的变量定义,没法了解到底该给这个 padding
传递一个什么样类型的值,有可能某个队友进行了以下操做:
padLeft('Hello world', true)
啪的一下,这个程序就崩了!因此你看,其实程序仍是很脆弱的。
为了更加明确的约束 padding
的类型,咱们有必要引进联合类型:
function padLeft(value: string, padding: string | number) { // ...中间同样 }
这个时候,咱们发现即便再来不少位队友,他们也知道该如何调用这个接口,由于编译器会强制队友写正确的类型,若是还继续写:
padLeft('Hello world', true)
编译器就会提示你以下错误:
这一小节中咱们学习了交叉类型和联合类型,它们是 TS 类型系统中的类型运算的产物,交叉类型是多个类型组成一个类型,最终结果类型是多个类型的总和,而联合类型是多个类型组成一个综合体,最终的结果类型是多个类型之中的某一个类型,交叉类型主要用于构造响应体,联合类型主要用于处理单变量被注解为多类型之一的场景,它还会与咱们下一节要讲的字面量类型发生化学反应,实现枚举和处理类型守卫,咱们将立刻来说解这些神奇的化学反应。
最后咱们来聊一聊类型守卫,类型守卫不少场景上都是和联合类型打配合存在的。在讲类型守卫的时候,咱们还须要先聊一聊字面量类型,额!其实这三者是相辅相成的。
其实字面量类型咱们在第二节中已经或多或少的提到过了,还记得那个报错嘛?
const tutureSlogan: string = 5201314 // 报错 Type '5201314' is not assignable to Type 'string'
这里的 TS 编译器提示,"Type '5201314' is not assignable to Type 'string“,这里的 "Type '5201314'" 实际上就是一个字面量类型。
字面量但是说是 TS 类型系统里面最小的类型,就像 JS 里面的数字 1,它不可能再拆成更小的部分了,通常字面量类型分为两种:
像 520
这个数,把它当作类型使用,它就是数组字面量类型,使用它来注解一个变量的时候是这样的:
let tuture: 520
当咱们初始化这个 tuture 变量的时候,就只能是赋值 520 这个数字了:
tuture = 520; // 正确 tuture = 521; // 错误 Type '521' is not assignable to type '520'
对应的字符串字面量相似,咱们如今用 '520'
这个字符串字面量类型来注解 tuture
:
let tuture: '520'; tuture = '520'; tuture = '521'; // Type '"521"' is not assignable to type '"520"'
能够看到字面量类型还带来一个特色就是,被注解的为对应字面量类型的变量,在赋值的时候只能赋值为这个被注解的字面量。
上面咱们了解了字面量类型,而且具体谈了谈它们的特色,那么这么一个单纯的类型,到底有什么特别的地方了?其实字面量类型搭配联合类型有意想不到的威力,咱们来举两个例子:
当咱们搭配联合类型和字面量类型的时候,咱们能够实现必定的枚举效果,咱们来看个例子,咱们买电脑通常都是三种系统,咱们能够经过选用这三种电脑类型来获取对应的一个用户的状况,咱们如今只给出一个函数的大致框架,具体实如今类型守卫里面详细展开:
function getUserInfo(osType: 'Linux' | 'Mac' | 'Windows') { // ... 后续实现 }
咱们能够看到上面的例子,osType
能够取三种操做系统之一的值,这就相似枚举,咱们能够建立一个相似的枚举:
enum EnumOSType { Linux, Mac, Windows } function getUserInfo(osType: EnumOSType) {}
上面两个例子效果其实差很少,咱们就经过 联合类型+字面量类型 实现了一个简单枚举的效果。
类型守卫是咱们 联合类型+字面量类型 的又一个应用场景,它主要用于在进行 ”联合“ 的多个类型之间,存在相同的字段,也存在不一样的字段,而后须要区分具体何时是使用哪一个类型,这么说可能比较迷糊,咱们来看个例子,加入咱们的 getUserInfo
函数的参数接收的是 os
,它根据 os.type
打印对应 os
携带的用户信息:
interface Linux { type: 'Linux'; linuxUserInfo: '极客'; } interface Mac { type: 'Mac'; macUserInfo: '极客+1'; } interface Windows { type: 'Windows'; windowsUserInfo: '极客+2'; } function getUserInfo(os: Linux | Mac | Windows) { console.log(os.linuxUserInfo); }
能够看到上面咱们将 osType
扩充成了 os
,而后三种 os 有相同的字段 type
和不一样的字段 xxxUserInfo
,可是当咱们函数体类打印 os.linuxUserInfo
的时候,TS 编译器报了以下错误:
有同窗就有疑问了,咱们这里不是联合类型了嘛,那应该 os
有 Linux
这一类型啊,这么打印为何会错呢?其实咱们要抓住一点,联合类型的最终结果是联合的多个类型之一,也就是 os
还多是 Mac
或者 Windows
,因此这里打印 os.linuxUserInfo
就有问题,因此咱们这个时候就须要类型守卫来帮忙了,它主要是根据多个类型中同样的字段,且这个字段是字面量类型来判断,进而执行不一样的逻辑来确保类型的执行是正确的,咱们来延伸一下上面的那个例子:
function getUserInfo(os: Linux | Mac | Windows) { switch (os.type) { case 'Linux': { console.log(os.linuxUserInfo); break; } case 'Mac': { console.log(os.macUserInfo); break; } case 'Windows': { console.log(os.windowsUserInfo); break; } } }
能够看到,若是有同窗跟着手敲这个函数的话,会发现当针对 os.type
进行条件判断以后,在 case
语句里面,TS 自动提示了须要取值的类型,好比在 Linux
case 语句里面输入 os.
会提示 linux
:
了解完字面量类型和类型守卫以后,咱们立刻运用在咱们的待办事项应用里面。
首先打开 src/TodoList.tsx
,咱们近一步完善 TodoList.tsx
的逻辑:
import React from "react"; import { List, Avatar, Menu, Dropdown, Modal } from "antd"; import { DownOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; import { ClickParam } from "antd/lib/menu"; import { Todo, getUserById } from "./utils/data"; const { confirm } = Modal; interface ActionProps { onClick: (key: "complete" | "delete") => void; isCompleted: boolean; } function Action({ onClick, isCompleted }: ActionProps) { const handleActionClick = ({ key }: ClickParam) => { if (key === "complete") { onClick("complete"); } else if (key === "delete") { onClick("delete"); } }; return ( <Menu onClick={handleActionClick}> <Menu.Item key="complete">{isCompleted ? "重作" : "完成"}</Menu.Item> <Menu.Item key="delete">删除</Menu.Item> </Menu> ); } interface TodoListProps { todoList: Todo[]; onClick: (todoId: string, key: "complete" | "delete") => void; } function TodoList({ todoList, onClick }: TodoListProps) { return ( <List className="demo-loadmore-list" // ... <List.Item key={item.id} actions={[ <Dropdown overlay={() => ( <Action isCompleted={item.isCompleted} onClick={(key: "complete" | "delete") => onClick(item.id, key) } /> )} > <a key="list-loadmore-more"> 操做 <DownOutlined /> </a> // ...
能够看到上面的改动主要有以下几个部分:
Action
组件,它接收两个参数,isCompleted
和 onClick
,前者用来标志如今对 Todo 操做是重作仍是完成,后者用来处理点击事件,根据 todo.id
和 操做的类型 key
来处理。Action
组件的 onClick
属性里面调用的 onClick
函数是父组件传下来的函数,因此咱们须要额外在 TodoListProps
加上这个 onClick
函数的类型定义,按照咱们以前学习的注解函数的知识,这里咱们须要注解参数列表和返回值,由于 onClick
函数内部执行点击逻辑,不须要返回值,因此咱们给它注解了 void
类型,针对参数列表,todoId
比较简单,通常是字符串,因此注解为 string
类型,而 key
标注操做的类型,它是一个字面量联合类型,容许有 complete
和 delete
两种ActionProps
来注解 Action 组件的参数列表,能够看到其中的 onClick
和咱们上一步讲解的同样,isCompleted
注解为 boolean
。onClick
的处理函数 handleActionClick
是一个ClickParam
类型,它是从 antd/lib/menu
导入的 ,由组件库提供的,而后咱们从参数里面解构出来了 key
,进而经过字面量类型进行类型守卫,处理了对于的 onClick
逻辑isCompleted
展现 “重作” 仍是 “完成”。改进了 src/TodoList.tsx
,接着咱们再来改进 src/App.tsx
里面对应于 TodoList
的逻辑,咱们打开 src/App.tsx
对其中的内容作出对应的修改以下:
import React, { useRef, useState } from "react"; import { List, Avatar, // ... function App() { const [todoList, setTodoList] = useState(todoListData); const callback = () => {}; // ... const activeTodoList = todoList.filter(todo => !todo.isCompleted); const completedTodoList = todoList.filter(todo => todo.isCompleted); const onClick = (todoId: string, key: "complete" | "delete") => { 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 className="container header"> // ... <div className="container"> <Tabs onChange={callback} type="card"> <TabPane tab="全部" key="1"> <TodoList todoList={todoList} onClick={onClick} /> </TabPane> <TabPane tab="进行中" key="2"> <TodoList todoList={activeTodoList} onClick={onClick} /> </TabPane> <TabPane tab="已完成" key="3"> <TodoList todoList={completedTodoList} onClick={onClick} /> </TabPane> </Tabs> </div> </div> ); } export default App;
能够看到上面主要就是两处改动:
TodoList
增长 onClick
属性onClick
函数,根据字面量类型 key
进行类型守卫处理对应的数据更改逻辑在这个小结中咱们学习了字面量类型和类型守卫,字面量类型与联合类型搭配能够实现枚举的效果,也能够处理类型守卫,字面量类型是 TS 中最原子的类型,它不能够再进行拆解,而类型守卫主要是在针对联合类型时,TS 编译器没法处理,须要经过开发者手工辅助 TS 编译器处理类型而存在。
想要学习更多精彩的实战技术教程?来 图雀社区逛逛吧。本文所涉及的源代码都放在了 Github 或者 Gitee 上,若是您以为咱们写得还不错,但愿您能给❤️这篇文章点赞Github 或 Gitee 仓库加星❤️哦~