前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript中那几个奇怪的知识点

TypeScript中那几个奇怪的知识点

作者头像
Peter谭金杰
发布2021-03-04 10:03:22
1.2K0
发布2021-03-04 10:03:22
举报
文章被收录于专栏:跨平台全栈俱乐部

写在开头

  • 我写了一年多TypeScript,总结了以下几个点,希望可以帮到大家
  • 如果感觉写得不错,记得来个关注/在看
比较容易遇到的问题
给一个对象添加属性
代码语言:javascript
复制
interface Obj {
  a: string;
}

const obj: Obj = {
  a: "1",
};

obj.b = 2;
  • 此时会出现错误提示:类型“Obj”上不存在属性“b”。
  • 要想解决这个问题,要使用索引签名
代码语言:javascript
复制
interface Obj {
  a: string;
  [index: string]: string | number;
}

const obj: Obj = {
  a: "1",
};

obj.b = 2;
  • 大家很好奇,为什么我这里会加入[index: string]: string | number;,类型是字符串或者数字。因为:

当你声明一个索引签名时,所有明确的成员都必须符合索引签名

函数重载
  • 场景:函数有多个参数,而且参数不确定时,函数运行逻辑不一致
代码语言:javascript
复制
// 重载
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
// Actual implementation that is a true representation of all the cases the function body needs to handle
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  } else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}

这样函数兼容 传 1、2、4个参数。 但是只要传三个,就会报错。

  • 函数重载最重要的是,最终声明(从函数内部看到的真正声明)与所有重载兼容(与上面的索引签名一致)
下载的第三方npm库没有ts声明文件
  • 例如:
代码语言:javascript
复制
npm i somePackage --save 
import somePackage from 'somePackage';
  • 但是此时提示:找不到模块“somePackage”或其相应的类型声明。
  • 此时你可以在项目根目录下新建index.d.ts,编写如下代码:
代码语言:javascript
复制
declare module 'somePackage';
...

这个问题迎刃而解

泛型
  • 这个问题很容易困扰小白,其实泛型简单来说,就是一个类型变量,如下所示:
代码语言:javascript
复制
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}

此时的T就是一个泛型,它是一个可变的类型。根据你传入niubi这个方法的参数对象来确定的,当我们传入的a是字符串,那么T就为string.返回的就是一个item为字符串的数组

代码语言:javascript
复制
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}

const obj = new Peter();

let res = obj.niubi("hehe");

res = 1;

res = ["2"];

此时res = 1会报错不能将类型“number”分配给类型“string[]”, 因为此时TS推断出来,res必定为一个数组,且里面的item是一个字符串.

res = ["2"]则不会报错

  • 泛型可以说是TS里面的一个难点,但是其实它只是一个可变的类型变量。
  • 调整参数后:
代码语言:javascript
复制
let res2 = obj.niubi(2);

res2 = 2;
  • 会报错:不能将类型“number”分配给类型“number[]”。

最后要记住的是,既然是类型变量。那么这个变量也可以是一个泛型。

代码语言:javascript
复制
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}

const obj = new Peter();

function test<T>(b: T): T {
  return b;
}

let res = obj.niubi(test(1));
  • 看到这里肯定有人会说,Peter你脱裤子放屁啊。这个还不如用any.那你再看下面这段代码,我们封装api请求的时候。
  • 首先定义好返回的接口。(返回的接口一般都是有统一的格式,状态码和result,data等)
代码语言:javascript
复制
// 请求接口数据
export interface ResponseData<T = any> {
  /**
   * 状态码
   * @type { number }
   */
  code: number;

  /**
   * 数据
   * @type { T }
   */
  result: T;

  /**
   * 消息
   * @type { string }
   */
  message: string;
}
  • 这里的data数据是动态的格式,我们可以用泛型来定义。
  • 这里用了两次泛型,先定义好返回的data数据,再用泛型方式传入,组装好返回的整个返回数据接口(包含code,result,data)。再接着传入到真正的请求函数中
代码语言:javascript
复制
// 在 axios.ts 文件中对 axios 进行了处理,例如添加通用配置、拦截器等
import Ax from './axios';

import { ResponseData } from './interface.ts';

export function getUser<T>() {
  return Ax.get<ResponseData<T>>('/somepath')
    .then(res => res.data)
    .catch(err => console.error(err));
}
  • 在真正的请求函数中使用了泛型,即传入任意类型参数<T>,那么便返回一个Promise风格的Promise<T>数据 :
代码语言:javascript
复制
const get = <T>(config: { url: string; headers?: { [key: string]: string } }): Promise<T> => {
  const fetchConfig = {
    method: 'GET',
    Accept: 'application/json',
    'Content-Type': 'application/json',
    ...(config.headers || {})
  };
  return fetch(config.url, fetchConfig).then<T>(response => response.json());
};

总结两次泛型的连续使用:

1.使用data作为泛型,传入

2.组装成{code,result,data}这种类型接口

3.将第二步的组装后类型作为泛型<T>传入get方法中

4.返回一个Promise风格的Promise<T>数据

这样做的意义,提取可变的数据类型data,让TS推断出这个接口返回的数据是怎么样的。减少不必要的重复代码,即每次接口调取都会返回的数据格式类型:coderesult

  • 相信你通过这段代码和文字,能真正理解TS的泛型如何用,什么地方使用,以及使用的意义了。
颗粒度定义类型后的问题
  • 当我们颗粒度比较细定义了接口以后,可能存在接口复用的问题,例如:
代码语言:javascript
复制
interface test1 {
  a: string;
}

interface test2 {
  b: string;
}
  • 此时我想要定义一个两个属性都拥有的对象,那么可以使用联合类型。
代码语言:javascript
复制
const obj: test1 & test2 = {
  a: "1",
  b: "2",
};
  • 如果我想定义一个只有a/b的对象,可以使用
代码语言:javascript
复制
const obj: test1 | test2 = {
  a: "1",
};

可能有人会说,怎么会写这么简单的东西。

  • 这里是为了接下来的类型兼容性打基础,TS里面最重要的就是type类型,类型系统就是它的核心。
  • 我们可以用两个不同的变量来互相赋值来检验,他们的类型是否兼容,例如:
代码语言:javascript
复制
interface Test1 {
  a: number;
  b: number;
  c: string;
}

interface Test2 {
  a: number;
  b: number;
}

let test1: Test1 = {
  a: 1,
  b: 2,
  c: "3",
};

let test2: Test2 = {
  a: 1,
  b: 2,
};

test1 = test2;

此时提示类型 "Test2" 中缺少属性 "c",但类型 "Test1" 中需要该属性。

  • 但是当我们用test1赋值给test2的时候:
代码语言:javascript
复制
test2 = test1;

这个时候是可以的

  • 这里其实隐藏着一些逻辑,Test1接口比Test2接口多一个c属性,Test2接口可以说是Test1接口的子类。这是多态性

关于如何处理、判断TS的类型兼容性,大家可以看下面这些类型

  • 协变(Covariant):只在同一个方向;
  • 逆变(Contravariant):只在相反的方向;
  • 双向协变(Bivariant):包括同一个方向和不同方向;
  • 不变(Invariant):如果类型不完全相同,则它们是不兼容的。
写在最后

javascript前端node.js

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 比较容易遇到的问题
    • 给一个对象添加属性
      • 函数重载
        • 下载的第三方npm库没有ts声明文件
          • 泛型
            • 颗粒度定义类型后的问题
            • 写在最后
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档