typescript学习要点(上) | 青训营笔记

148 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

目录简介

  1. Typescript 见解
  1. Typescript 基础语法,包括类型、泛型、类型别名、类型字面量等
  1. Typescript 高级类型讲解及实例
  1. Typescript 工程应用介绍

1.背景

1.1 发展历史

  • 2012-10:微软发布了 TypeScript 第一个版本(0.8)
  • 2014-10:Angular发布了基于 TypeScript 的 2.0 版本
  • 2015-04:微软发布了 Visual Studio Code
  • 2016-05: @types/react 发布,TypeScript 可开发 React
  • 2020-09:Vue 发布了 3.0 版本,官方支持 TypeScript
  • 2021-11:v4.5版本发布

总结:

  1. 到目前为止,三大前端框架均已支持TS
  2. Visual Studio Code 是最适合进行TS开发的软件

1.2 为何我们需要TS

语言动静强弱
JavaScript动态类型弱类型
TypeScript静态类型弱类型
  • 静态类型:编译阶段类型检查,编译器负责。

  • 动态类型:运行时类型检查,程序员负责。

  • 弱类型:数字和字符串是可以相加的,因为背后执行了类型转换

  • 强类型:数字和字符串是不可以相加的

静态类型

  1. 可读性增强: 基于语法解析TSDoc,ide增强,便于生成文档
  2. 可维护性增强: 在编译阶段暴露大部分错误
  3. 多人合作的大型项目中,获得更好的稳定性和开发效率

JS的超集

  1. 包含于兼容所有Js特性,支持共存
  2. 支持渐进式引入与升级

在TS官网的在线编辑器快速体验

2.基本语法

2.1 基础数据类型

JS image.png

TS image.png

2.2 对象类型

image.png image.png

2.3 函数类型

image.png

afb838567c5087fd50e566ae2a56c60.jpg

image.png

2.4 函数重载

image.png 根据传入的type参数的值来决定返回值的类型

函数重载是指在同一个作用域内,有多个函数名相同,但是形参列表不同(参数类型不同,参数个数不同,参数顺序不同),返回值无关,我们将这种叫做重载函数。重载的函数是通过形参列表区分的,和其他无关。一句话来说“一个接口,多种实现”,不仅函数可以重载,运算符也可以重载。

19edb3e167bf4de503f84aba9c625c5.jpg

例如:如果要实现一个加法运算,加法函数可以是整型也可以是浮点型,就可以使用函数重载实现。 这样实现后,我们进行加法运算,则只需要调用ADD函数,编译器会根据我们传递实参的类型和个数,顺序判断想要调用那个ADD函数。

image.png

2.5 数组类型

数组是特殊的对象

image.png

Record<Keys, Type> Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.

interface CatInfo {
  age: number;
  breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

2.6 typescript补充类型

image.png

2.7 typescript泛型

image.png 用any是无法达到要求的,因为我们传入1个数组,则希望返回的元素也是一个数组,如果用any,则类型信息其实丢失了 image.png

image.png

2.8 类型别名与类型断言

image.png

把类型全部写出来是很复杂的,用type关键字来命名它

2.9 字符串、数字 字面量

image.png 用类似于或的符号来约定:必须是其中的一个

3.高级类型

3.1 联合/交叉类型

为书籍列表编写类型

 const bookList = [{ 
   author: 'xiaoming', 
   type: 'history',
   range: '2001-2021',
   },{
   author: 'xiaoli', 
   type: 'story', 
   theme: 'love',
   }]

2个对象的类型其实有点重复了 而且type的类型其实不是string,其实是只能在“love”或者“history”中二选一

interface IHistoryBook { 
author: string; 
type: string; 
range: string
}

interface IStoryBook { 
author: string; 
type: string; 
theme: string;
}

type lBookList = Array<IHistoryBook | IStoryBook>;
  • 联合类型:IA|IB;联合类型表示一个值可以是几种类型之一
  • 交叉类型:IA &IB;多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type lBookList = Array<{ 
  author: string;
}&({
  type: 'history'; 
  range: string;
}|{
  type: 'story'; 
  theme: string;
})>

3.2 类型保护与类型守卫

interface lA { a: 1, a1: 2 }
interface lB { b: 1, b1: 2 }
function log(arg: IA | IB) {
/*报错: 类型“IA|IB”上不存在属性“a”。类型“IB”上不存在属性“a”。 */
/*结论: 访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分*/
if (arg.a) {
console.log(arg.a1)
} else {
console.log(arg.b1);
}
}

访问联合类型时,只能访问共有的属性和方法,比如这里可以访问toString等对象都有的方法

7a79a97d5228ee02be84c8c3c714c26.png

所以本质上是把前一种方法的是非判断改进了一下

注意这里的!!的用法

!可将变量转换成布尔类型,对nullundefined和空字符串取反后均为true,对于非空字符串或非null对象,执行!运算后则为false,例如:

!null = true;
!undefined = true;
!'' = true;
!'wang' = false;

!!其实就是取非后再取非,常常用来做非空判断。!!常常可以简化代码,例如需要判断变量val为非null,未定义或者非空串才能执行方法体

var val;
if(val!=null&&typeof(val)!=undefined&&val!=''){
    console.log('执行...');
}

利用!!可以写的非常简洁

if(!!val){
    console.log('执行...');
}

类型保护

// 实现函数reverse
// 其可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
/* typof 类型保护 */
if (typeof target == = 'string') {
return target.split('').reverse().join('");
}
/* instance 类型保护 */
if (target instanceof Object) { 
return target.reverse();
}
}

这里在使用typeof或者instanceof时,并没有进行类型守卫也没有报错,因为TS智能识别了这2个词,进行了类型保护

type lBookList = Array<{ 
  author: string;
}&({
  type: 'history'; 
  range: string;
}|{
  type: 'story'; 
  theme: string;
})>

// 实现函数logBook类型
// 函数接受书本类型,并logger出相关特征 
function logBook(book: IBookltem) {
// 联合类型 +类型保护 =自动类型推断 
if (book.type === 'history') { 
console.log(book.range)
} else {
console.log(book.theme
);

3.3 高级类型

/**
实现merge函数类型
要求sourceObj必须为targetObj的子集
*/
function merge1(sourceObj, targetObj) { 
const result = {...sourceObj}; 
for(let key in targetObj) {
  const itemVal = sourceObj[key];
  itemVal &&(result[key] = itemVal );//注意这里&&的用法
  }
  return result;
 }

function merge2(sourceObj, targetObj) { 
 return { .….sourceObj, …targetObj};
}

Logical AND (&&)

分为3个层次来理解

1.执行布尔 AND 运算

当与布尔操作数一起使用时,&& 对两个值执行布尔 AND 运算:当且仅当它的第一个操作数和它的第二个操作数都为真时,它才返回真。 如果这些操作数中的一个或两个为假,则返回假。

&& 通常用作连接两个关系表达式的连词

x === 0 && y === 0 // true if, and only if, x and y are both 0

关系表达式总是计算为真或假,因此当这样使用时, && 运算符本身返回真或假

关系运算符的优先级高于 &&(和 ||),因此可以安全地编写此类表达式而无需括号。

2.作为真值和假值的布尔 AND 运算符

  • && 不要求它的操作数是布尔值。
  • 所有 JavaScript 值要么是“真实的truthy”,要么是“虚假的falsy”。
  • 在 JavaScript 中,任何需要布尔值的表达式或语句都可以接受真值或假值

如果两个操作数都是真值,则运算符返回一个真值returns a truthy value。

否则,一个或两个操作数必然是假的,并且运算符返回一个假值returns a falsy value。

3.捷径

该运算符首先计算其第一个操作数,即其左侧的表达式。

如果左边的值是假的falsy,那么整个表达式的值也一定是假的,所以&&只是简单地返回左边的值,甚至不计算右边的表达式。

另一方面,如果左边的值是真值,那么表达式的整体值取决于右边的值。如果右边的值是真实的,那么整体的价值一定是真实的, 如果右边的值是假的,那么整体的价值一定是假的。

所以当左边的值是真的时,&& 运算符计算并返回右边的值

let o = {x: 1}; 
let p = null; 
o && o.x // => 1: o is truthy, so return value of o.x **返回的不是truthy或者falsy,返回的就是本来的值**
p && p.x // => null: p is falsy, so return it and don't evaluate p

以下两行代码等效

if (a === b) stop(); // Invoke stop() only if a === b 
(a === b) && stop(); // This does the same thing

通常,当您在 && 的右侧编写带有副作用(赋值、递增、递减或函数调用)的表达式时,您必须小心。

这些副作用是否发生取决于左侧的值

interface ISourceObj { x?: string; y?: string;
interface lTargetObj { x: string; y: string
type lMerge = (sourceObj: ISourceObj, targetObj: ITargetObj) = > ITargetObj;
/**
类型实现繁琐: 若obj类型较为复杂,则声明source和target便需要大量重复2遍*容易出错: 若target增加/减少key,则需要source联动去除*/

在定义时是不明确的,在使用时才会明确,我们也不知道函数的使用人会传入一个怎样的对象

未完待续

image.png