项目中使用TypeScript的TodoList实例详解
FE情报局 人气:0为什么用todolist
现代的框架教程目前再也不是写个hello world那么简单了,而是需要有一定基础能力能够做到数据绑定、遍历、条件判断等各种逻辑,而能完成这一系列内容的,todolist就是个很好的实现,比如react的教程、solijs教程都是以todolist为例
当然,你如果想看各种框架实现todolist的话,你可以访问TodoMVC 这里面展示了各种框…
todolist的ts化
但是对于ts教程来说,只有官方的一些实例,并没有一个很好的项目上的教程,也就是有关实战的部分,很多同学在学习了ts之后,只会一些基础的js类型的设置,放在项目中就不清楚了,所以我们就出了这个教程
当然在开始之前,我们要了解这个教程不依赖任何的前端库,比如react,vue等,同时也为了节省时间,我们仅仅是放出一些关键的ts代码,不需要将整个应用都展示出来,同样能够让你知道ts的使用
数据到视图
一个tudolist对应的数据是怎么样的?就拿刚才的视图来看的话,它应该是一个对象数组,数据应该是这样的
[ { id: 1, text: '待办事项1', done: false }, { id: 2, text: '待办事项2', done: false }, { id: 3, text: '待办事项3', done: false } ]
其中id是每一个代办事项的唯一标识,text是事项名称,done表示是否完成
当我们点击完成的时候,实际上就是每一项的done发生了变化,数据发生变化之后驱动我们的视图做出对应的改变
实现handleTodoItem
对应的上述的点击事件,我们实现一下它的伪代码,当其点击的时候,需要处理对应的数据,先使用js实现
function handleTodoItem(todo){ // 点击的时候todo中的done的布尔值取反 return { ...todo, done: !todo.done } }
然后我们使用ts进行优化
type Todo = { id: number; text: string; done: boolean; } // 如果某个变量是todo类型,可以这样 const todoItem: Todo = { id: 1, text: '待办事项1', done: false }
这样ts类型就是正常的,如果相应的todoItem不匹配,则编译就会发生错误,可以让错误提前感知,并且如果项目中有配置的ts相关,vscode中就会给出对应的错误信息
对应到handleTodoItem这个方法中,应该怎么写呢?
function handleTodoItem(todo: Todo): Todo { // 逻辑实现 }
readonly
对于handleTodoItem这个函数来说,函数应该是无副作用的,所以传进去的todo对象,不应该发生变化,而是返回一个新的对象
比如这种方法,虽然能够实现同样的内容,但是它是有副作用的,改变了传入的参数,是不可取的
function handleTodoItem(todo: Todo):Todo { // 点击的时候todo中的done的布尔值取反 todo.done = !todo.done return todo }
但是这种的ts并不会报错,怎么办?那就需要借助我们的ts进行类型校验,你可以这样
type Todo = { readonly id: number; readonly text: string; readonly done: boolean; }
当你尝试修改修改的话,就会发生ts错误,不允许修改,因为Todo类型是只读的,当然你也可以这样设置对象中所有的属性为只读
type Todo = Readonly<{ id: number; text: string; done: boolean; }>
在ts中,这种Readonly的关键词还有很多,比如Required,Partial等,如有需要,大家可自行搜索
分类
对于已经完成的list,我们需要将其进行分类筛选,比如我们要筛选出所有已经完成的项目,那么表现就是一个数组,并且done为true
[ { id: 1, text: '待办事项1', done: true }, { id: 2, text: '待办事项2', done: true } ]
如何表示一个数组类内容呢?Todo[]
这种方式就能表示上述数据,同样的,函数的参数是不允许修改的,避免副作用,所以可以这样
function completeTodoList( todos: readonly Todo[] ): Todo[] { // ... }
当然,由于Todo的type中的done为boolean,但是在completeTodoList中done的值为true,所以我们需要重新定义一个类型
type CompletedTodo = Readonly<{ id: number; text: string; done: true; }>
所以上述的方法就会变成
function completeTodoList( todos: readonly Todo[] ): CompletedTodo[] { // ... }
交叉类型
对于上面的Todo和CompletedTodo类型,其中这两个类型的id和text都是重复的,我们可以删除重复的逻辑,使用交叉类型
举个例子
type A = {a: number} type B = {b: string} type AandB = A & B // 结果为 // { // a: number // b: string // }
当两个类型key相同时,第二个key会覆盖掉第一个的内容
type A = {key: number} type B = {key: string} type AandB = A & B // 结果为 // { // key: string // }
那针对Todo和CompletedTodo类型,我们想从Todo通过交叉类型得到CompletedTodo,该怎么做呢?
type CompletedTodo = Todo & { readonly done: true }
是不是很简洁,并且去除了一些重复代码
新增功能
如果在Todo的基础上,我们新增了一个功能,对应的todo的优先级,使用priority这个字段表示
并且一共有三种优先级
!!!
!!
!
你可以priority: 2
这样设置,展示为【!!】当然你也可以自定义,比如
{ priority: { custom: '紧急' } }
则展示为【紧急】,所以这时候数据变成了
[ { id: 1, text: '待办事项1', done: false, priority: 1 }, { id: 2, text: '待办事项2', done: false, priority: 2 }, { id: 3, text: '待办事项3', done: false, priority: { custom: '紧急' } }, { id: 4, text: '待办事项4', done: false }, ]
我们已经有了Todo类型,如何新增一个key呢?
联合类型
上面我们之后交叉类型通过 &
连接,那联合类型则是通过 |
连接,同样的举个例子
type Foo = number | string;
这表示Foo类型可以是一个数字,也可以是一个string类型,所以我们的priority类型可以这样设置
type Priority = 1 | 2 | 3 | { custom: string };
这个时候priority就是我们想要的内容了,所以todo的类型可以变一下
type Todo = Readonly<{ id: number; text: string; done: boolean; priority: Priority; }>
可选属性
上面的priority这个属性目前是必填的,但是这个属性我们可以不写,也就是todo可以没有优先级,针对这种情况,我们可以使用可选属性
type Todo = Readonly<{ id: number; text: string; done: boolean; priority?: Priority; }>
在对应的地方添加一个?即可
数据转视图
那对应的priority的数据有了,如何把1,2,3这种的转成!!!的形式呢?
可以自定义一个函数,也就是priorityToString
priorityToString(1) // ! priorityToString(2) // !! priorityToString(3) // !!! priorityToString({ custom: '紧急' }) // 紧急
情况比较少,可能你会这样写
function priorityToString(priority: Priority): string { if(priority === 1){ return '!' }else if(priority === 2){ return '!!' }else if(priority === 3){ return '!!!' }else{ return priority.custom } }
联合类型我们通过if条件进行判断的时候,它会自动确认每个if条件下的参数类型,这也是联合类型的强大之处
总结
基本上我们项目中用到的一些知识点这里都概括了,通过一个简单的项目,将ts的一些基本类型给大家做了一个简要的说明
加载全部内容