TypeScript 学习笔记(二):接口与类型别名、字面量类型

这篇具有很好参考价值的文章主要介绍了TypeScript 学习笔记(二):接口与类型别名、字面量类型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、接口的定义

在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

二、接口的用途

接口的用途就是对行为和动作进行规范和约束,跟抽象类有点像,但是,接口中不能有方法体,只允许有方法定义。

三、接口用法:

1. 使用interface来定义接口:

  interface Info {
    firstName: string;
    lastName: string;
  }
  const getFullName = ({ firstName, lastName }: Info) => {
    return `${firstName} ${lastName}`;
  };
  console.log(getFullName({ firstName: '123', lastName: '1231' }));

注意在定义接口的时候,你不要把它理解为是在定义一个对象,而要理解为{}括号包裹的是一个代码块,里面是一条条声明语句,只不过声明的不是变量的值而是类型。声明也不用等号赋值,而是冒号指定类型。每条声明之前用换行分隔即可,或者也可以使用分号或者逗号,都是可以的。

2. 可选属性

接口设置可选属性,在属性名后面加个?即可:

interface Vegetables {
  color?: string;
  type: string;
}
 

3. 多余属性检查

getVegetables({
  type: "tomato",
  size: "big" // 'size'不在类型'Vegetables'中
});
 

我们看到,传入的参数没有 color 属性,但也没有错误,因为它是可选属性。但是我们多传入了一个 size 属性,这同样会报错,TypeScript 会告诉你,接口上不存在你多余的这个属性。只要接口中没有定义这个属性,就会报错,但如果你定义了可选属性 size,那么上面的例子就不会报错。

4. 绕开多余属性检查

  • 什么是接口的多余参数检查
interface Baseinfo {
    name:string,
    sex?:string
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {
    console.log(`姓名:${parmasinfo.name }`)
}

// 如果直接传递参数,且传递的参数key未在接口中定义会提示错误
printPesonInfo( {name:'wang',age:13} ) // 报错的

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记

  • 使用类型断言

类型断言就是用来明确告诉 TypeScript,我们已经自行进行了检查,确保这个类型没有问题,希望 TypeScript 对此不进行检查,所以最简单的方式就是使用类型断言:

interface Baseinfo {
    name:string,
    sex?:string
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {
    console.log(`姓名:${parmasinfo.name }`)
}

// 利用类型断言,告诉编译器我们传递的参数 就是Baseinfo 接口的东西
printPesonInfo( {name:'wang',age:13} as Baseinfo ) // wang
  • 索引签名
interface Baseinfo {
    name:string,
    sex?:string,
    [other:string]:any
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {
    console.log(`姓名:${parmasinfo.name }`)
}

// 接口中的索引签名other 就会收到age
printPesonInfo( {name:'wang',age:13}) // wang
  • 利用类型兼容性
interface Baseinfo {
    name:string,
    sex?:string,
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {
    console.log(`姓名:${parmasinfo.name }`)
}

let paramsinfo = {name:'wang',age:13} 
// 类型兼容性就是我们定义的paramsinfo 不管有都少东西,只要包含接口中定义的即可
printPesonInfo(paramsinfo) // 姓名:wang

5. 只读属性

关键字:readonly

const NAME: string = "Lison";
NAME = "Haha"; // Uncaught TypeError: Assignment to constant variable
 
const obj = {
  name: "lison"
};
obj.name = "Haha";
 
interface Info {
  readonly name: string;
}
const info: Info = {
  name: "Lison"
};
info["name"] = "Haha"; // Cannot assign to 'name' because it is a read-only property
 

6. 函数类型

接口可以描述普通对象,还可以描述函数类型,我们先看写法:

interface AddFunc {
  (num1: number, num2: number): number;
}

这里我们定义了一个AddFunc结构,这个结构要求实现这个结构的值,必须包含一个和结构里定义的函数一样参数、一样返回值的方法,或者这个值就是符合这个函数要求的函数。我们管花括号里包着的内容为调用签名,它由带有参数类型的参数列表和返回值类型组成。后面学到类型别名一节时我们还会学习其他写法。来看下如何使用:

const add: AddFunc = (n1, n2) => n1 + n2;
const join: AddFunc = (n1, n2) => ${n1} ${n2}; // 不能将类型'string'分配给类型'number'
add("a", 2); // 类型'string'的参数不能赋给类型'number'的参数
 

上面我们定义的add函数接收两个数值类型的参数,返回的结果也是数值类型,所以没有问题。而join函数参数类型没错,但是返回的是字符串,所以会报错。而当我们调用add函数时,传入的参数如果和接口定义的类型不一致,也会报错。

你应该注意到了,实际定义函数的时候,名字是无需和接口中参数名相同的,只需要位置对应即可。

四、接口的高阶用法

1. 索引类型

我们可以使用接口描述索引的类型和通过索引得到的值的类型,比如一个数组[‘a’, ‘b’],数字索引0对应的通过索引得到的值为’a’。我们可以同时给索引和值都设置类型,看下面的示例:

  interface RoleDic {
    [id: number]: string;
  }
  const role1: RoleDic = {
    0: "superadmin",
    1: "admin"
  };
  console.log(role1);
  const role2: RoleDic = {
    s: "superadmin",  // error 不能将类型"{ s: string; a: string; }"分配给类型"RoleDic"。
    a: "admin"
  };
  console.log(role2);
  const role3: RoleDic = ["super_admin", "admin"];
  console.log(role3);
 

role2 报错信息:
TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记
上面的例子中 role3 定义了一个数组,索引为数值类型,值为字符串类型。

你也可以给索引设置readonly,从而防止索引返回值被修改。

interface RoleDic {
  readonly [id: number]: string;
}
const role: RoleDic = {
  0: "super_admin"
};
role[0] = "admin"; // error 类型"RoleDic"中的索引签名仅允许读取
 

这里有的点需要注意,你可以设置索引类型为 number。但是这样如果你将属性名设置为字符串类型,则会报错;但是如果你设置索引类型为字符串类型,那么即便你的属性名设置的是数值类型,也没问题。因为 JS 在访问属性值的时候,如果属性名是数值类型,会先将数值类型转为字符串,然后再去访问。你可以看下这个例子:

const obj = {
  123: "a", // 这里定义一个数值类型的123这个属性
  "123": "b" // 这里在定义一个字符串类型的123这个属性,这里会报错:标识符“"123"”重复。
};
console.log(obj); // { '123': 'b' }
 

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记
如果数值类型的属性名不会转为字符串类型,那么这里数值123和字符串123是不同的两个值,则最后对象obj应该同时有这两个属性;但是实际打印出来的obj只有一个属性,属性名为字符串"123",而且值为"b",说明数值类型属性名123被覆盖掉了,就是因为它被转为了字符串类型属性名"123";又因为一个对象中多个相同属性名的属性,定义在后面的会覆盖前面的,所以结果就是obj只保留了后面定义的属性值。

2. 继承接口

接口可以继承,这和类一样,这提高了接口的可复用性。来看一个场景:

我们定义一个Vegetables接口,它会对color属性进行限制。再定义两个接口,一个为Tomato,一个为Carrot,这两个类都需要对color进行限制,而各自又有各自独有的属性限制,我们可以这样定义:

interface Vegetables {
  color: string;
}
interface Tomato {
  color: string;
  radius: number;
}
interface Carrot {
  color: string;
  length: number;
}

三个接口中都有对color的定义,但是这样写很繁琐,所以我们可以用继承来改写:

 interface Vegetables {
  color: string;
}
interface Tomato extends Vegetables {
  radius: number;
}
interface Carrot extends Vegetables {
  length: number;
}
const tomato: Tomato = {
  radius: 1.2 // error  Property 'color' is missing in type '{ radius: number; }'
};
const carrot: Carrot = {
  color: "orange",
  length: 20
};

上面定义的 tomato 变量因为缺少了从Vegetables接口继承来的 color 属性,从而报错。

一个接口可以被多个接口继承,同样,一个接口也可以继承多个接口,多个接口用逗号隔开。比如我们再定义一个Food接口,Tomato 也可以继承 Food:

interface Vegetables {
  color: string;
}
interface Food {
  type: string;
}
interface Tomato extends Food, Vegetables {
  radius: number;
}
 
const tomato: Tomato = {
  type: "vegetables",
  color: "red",
  radius: 1.2
};  // 在定义tomato变量时将继承过来的color和type属性同时声明
 

3. 混合类型接口

JS 中,函数是对象类型。对象可以有属性,所以有时我们的一个对象,它既是一个函数,也包含一些属性。比如我们要实现一个计数器函数,比较直接的做法是定义一个函数和一个全局变量:

let count = 0;
const countUp = () => count++;
 

但是这种方法需要在函数外面定义一个变量,更优一点的方法是使用闭包:

// javascript
const countUp = (() => {
  let count = 0;
  return () => {
    return ++count;
  };
})();
console.log(countUp()); // 1
console.log(countUp()); // 2
 
// javascript
let countUp = () => {
  return ++countUp.count;
};
countUp.count = 0;
console.log(countUp()); // 1
console.log(countUp()); // 2
 

我们可以看到,我们把一个函数赋值给countUp,又给它绑定了一个属性count,我们的计数保存在这个 count 属性中。

我们可以使用混合类型接口来指定上面例子中 countUp 的类型:

interface Counter {
  (): void; // 这里定义Counter这个结构必须包含一个函数,函数的要求是无参数,返回值为void,即无返回值
  count: number; // 而且这个结构还必须包含一个名为count、值的类型为number类型的属性
}
const getCounter = (): Counter => { // 这里定义一个函数用来返回这个计数器
  const c = () => { // 定义一个函数,逻辑和前面例子的一样
    c.count++;
  };
  c.count = 0; // 再给这个函数添加一个count属性初始值为0
  return c; // 最后返回这个函数对象
};
const counter: Counter = getCounter(); // 通过getCounter函数得到这个计数器
counter();
console.log(counter.count); // 1
counter();
console.log(counter.count); // 2
 

上面的例子中,getCounter函数返回值类型为Counter,它是一个函数,无返回值,即返回值类型为void,它还包含一个属性count,属性返回值类型为number。

五、类型别名

类型别名就是给一种类型起个别的名字,之后只要使用这个类型的地方,都可以用这个名字作为类型代替,但是它只是起了一个名字,并不是创建了一个新类型。这种感觉就像 JS 中对象的赋值,你可以把一个对象赋给一个变量,使用这个对象的地方都可以用这个变量代替,但你并不是创建了一个新对象,而是通过引用来使用这个对象。

使用 type 关键字,定义类型别名:

type TypeString = string;
let str: TypeString;
str = 123; // error Type '123' is not assignable to type 'string'
 
  • 类型别名也可以使用泛型:
type PositionType<T> = { x: T; y: T };
const position1: PositionType<number> = {
  x: 1,
  y: -1
};
const position2: PositionType<string> = {
  x: "right",
  y: "top"
};
 
  • 使用类型别名时也可以在属性中引用自己:
type Child<T> = {
  current: T;
  child?: Child<T>;
};
let ccc: Child<string> = {
  current: "first",
  child: {
    // error
    current: "second",
    child: {
      current: "third",
      child: "test" // 这个地方不符合type,造成最外层child处报错
    }
  }
};
 

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记

  • 但是要注意,只可以在对象属性中引用类型别名自己,不能直接使用,比如下面这样是不对的:
type Child = Child[]; // error 类型别名“Child”循环引用自身

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记
因为类型别名只是为其它类型起了个新名字来引用这个类型,所以当它为接口起别名时,不能使用 extendsimplements

  • 接口和类型别名有时可以起到同样作用,比如下面这个例子:
type Alias = {
  num: number;
};
interface Interface {
  num: number;
}
let alias: Alias = {
  num: 123
};
let interface: Interface = {
  num: 321
};
alias = interface;
 

可以看到用类型别名和接口都可以定义一个只包含 num 属性的对象类型,而且类型是兼容的。那么什么时候用类型别名,什么时候用接口呢?可以通过两点来选择:

  1. 当你定义的类型要用于拓展,即使用 implements 等修饰符时,用接口。
  2. 当无法通过接口,并且需要使用联合类型或元组类型,用类型别名。

六、字面量类型

1. 字符串字面量类型

字符串字面量类型其实就是字符串常量,与字符串类型不同的是它是具体的值。

type Name = "Lison";
const name1: Name = "test"; // error 不能将类型“"test"”分配给类型“"Lison"”
console.log(name1);
const name2: Name = "Lison";
console.log(name2); 

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记
使用联合类型来使用多个字符串:

type Direction = "north" | "east" | "south" | "west";
function getDirectionFirstLetter(direction: Direction) {
  return direction.substr(0, 1);
}
getDirectionFirstLetter("test"); // error 类型“"test"”的参数不能赋给类型“Direction”的参数
getDirectionFirstLetter("east");
 

2. 数字字面量类型

另一个字面量类型就是数字字面量类型,它和字符串字面量类型差不多,都是指定类型为具体的值。

type Age = 18;
interface Info {
  name: string;
  age: Age;
}
const info: Info = {
  name: "Lison",
  age: 28 // error 不能将类型“28”分配给类型“18”
};
 

这里补充一个比较经典的逻辑错误,来看例子:

function getValue(index: number) {
  if (index !== 0 || index !== 1) {
    // error This condition will always return 'true' since the types '0' and '1' have no overlap
    // ...
  }
}
 

TypeScript 学习笔记(二):接口与类型别名、字面量类型,TypeScript,typescript,学习,笔记

这个例子中,在判断逻辑处使用了 || 符,当 index !== 0 不成立时,说明 index 就是 0,则不应该再判断 index 是否不等于 1;而如果 index !== 0 成立,那后面的判断也不会再执行;所以这个地方会报错。

七、Interface 与 Type 的区别

1. 区别

  1. 接口可以重复定义的接口类型,它的属性会叠加,类型别名不行
interface Language {
  id: number
}

interface Language {
  name: string
}

let lang: Language = {
  id: 1, // ok

  name: 'name', // ok
}

// 如果使用类型别名
/** ts(2300) 重复的标志 */
type Language = {
  id: number
}

/** ts(2300) 重复的标志 */
type Language = {
  name: string
}
let lang: Language = {
  id: 1,
  name: 'name',
}
  1. type 可以使用联合类型和交集,interface 不能使用联合类型和交集组合
type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
  1. type 支持类型映射,interface不支持
type Keys = "firstname" | "surname"

type DudeType = {
  [key in Keys]: string
}

const test: DudeType = {
  firstname: "Pawel",
  surname: "Grzybek"
}

// 报错
//interface DudeType {
//  [key in keys]: string
//}

2. 相同点

都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface , 虽然效果差不多,但是两者语法不同。

  • interface extends interface
interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
  • type extends type
type Name = { 
  name: string; 
}
type User = Name & { age: number  };
  • interface extends type
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
  • type extends interface
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

总结:
interface 只能用于定义对象类型和方法,而 type 的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛。

但是interface也有其特定的用处:
interface 方式可以实现接口的 extends 和 implements
interface 可以实现接口合并声明文章来源地址https://www.toymoban.com/news/detail-566939.html

到了这里,关于TypeScript 学习笔记(二):接口与类型别名、字面量类型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TypeScript高级类型:联合类型、交叉类型和类型别名

    TypeScript 是一门强类型语言,其高级类型功能使得开发者能够更加灵活地定义类型并对其进行操作,以便于更好地编写可维护和可扩展的代码。 在本文中,将着重讨论三种高级类型:联合类型、交叉类型和类型别名。我们将详细介绍这些类型,并且还会提供一些有用的代码示

    2024年02月10日
    浏览(29)
  • 【TypeScript】类型推断与类型别名的使用方式。

    什么是类型推断? 在 TypeScript 中, 如果声明变量时, 没有明确的指定类型 ,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。 以下代码虽然 没有明确指定类型 ,但是会在编译的时候报错: 事实上,它等价于: TypeScript 会在没有明确的指定类型的时候,

    2024年02月16日
    浏览(36)
  • TypeScript 类型别名(Type Aliases)

    在 TypeScript 中,类型别名(Type Aliases)是一种给现有类型起别名的方式。它可以帮助提高代码的可读性和可维护性,尤其是当你需要使用复杂或重复的类型注解时。 基本使用 本段代码使用了类型别名  N1  来表示一个可以是  number 、 string  或者  boolean  类型的联合类型。接

    2024年02月13日
    浏览(26)
  • TypeScript 学习笔记(一):类型

    TS中实现对象属性必选、对象属性在开发过程中十分常见,前端在传参数时,有些参数比必传,有些是选传,我们可以定一个多个对象来实现传参,但是这让代码变得冗余。我们可以通过TS定义数据类型来实现。 TypeScript中文网 1. 数组 2. 布尔 3. 数值 当我们给num赋值为123但没有

    2024年02月13日
    浏览(29)
  • TypeScript 学习笔记(七):条件类型

    TS中的条件类型就是在类型中添加条件分支,以支持更加灵活的泛型,满足更多的使用场景。内置条件类型是TS内部封装好的一些类型处理,使用起来更加便利。 当T类型可以赋值给U类型时,则返回X类型,否则返回Y类型。 T U X Y 四个是占位符,分别表示四种类型; T extends U

    2024年02月17日
    浏览(29)
  • TypeScript 学习笔记(四):类型守卫

    类型守卫的作用在于触发类型缩小。实际上,它还可以用来区分类型集合中的不同成员 类型守卫包括switch、字面量恒等、typeof、instanceof、in 和自定义类型守卫 简单说当一个类型是多种可能时例如’any’,‘unknown’,‘联合类型’ 等在逻辑判断时候要具体到其唯一子集可能性

    2024年02月15日
    浏览(48)
  • TypeScript 学习笔记(六):索引签名类型、映射类型

    keyof 可以用于获取某种类型的所有键,其返回类型是联合类型。 keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键 通过例子可以看到,这里的keyof Info其实相当于\\\"name\\\" | “age”。通过和泛型结合使用,TS 就可以检查使用了动态属性名的代码: 接口 基本数据类型 类 如果

    2024年02月17日
    浏览(35)
  • TypeScript 学习笔记(一):基本类型、交叉类型、联合类型、类型断言

    TS中实现对象属性必选、对象属性在开发过程中十分常见,前端在传参数时,有些参数比必传,有些是选传,我们可以定一个多个对象来实现传参,但是这让代码变得冗余。我们可以通过TS定义数据类型来实现。 TypeScript中文网 1. 数组 2. 布尔 3. 数值 当我们给num赋值为123但没有

    2024年02月15日
    浏览(35)
  • 【TypeScript】TS接口interface类型(三)

    一、前言 TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。这些方法都应该是抽象的,需要由具体的类去实现,然后第三方

    2024年02月14日
    浏览(34)
  • 【TypeScript】接口类型 Interfaces 的使用理解

    导语 : 什么是 类型接口 ? 在面向对象语言中 ,接口 (Interfaces) 是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。TypeScript 中的 类型接口 是一个非常灵活的概念,除了可用于 对类的一部分行为进行抽象 以外,也常用于对「

    2024年02月15日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包