写点什么

前端 10 问之 TypeScript (第一篇)

用户头像
局外人
关注
发布于: 2020 年 09 月 11 日
前端 10 问之 TypeScript (第一篇)

1、什么是 Typescript?


TypeScript 是 JavaScript 的一个超集,主要为 JavaScript 提供了类型系统,它由 Microsoft 开发,代码是开源的。


2、interface 和 type 的区别


相同点


  • 都可以描述一个对象或者函数


interface

interface User {  name: string  age: number}
interface SetUser { (name: string, age: number): void;}
复制代码


type

type User = {  name: string  age: number};
type SetUser = (name: string, age: number): void;
复制代码


不同点


type 可以而 interface 不行


  • type 可以声明基本类型别名,联合类型

// 基本类型别名type Name = string
// 联合类型interface Dog { wong();}interface Cat { miao();}
type Pet = Dog | Cat
复制代码
  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值

// 当你想获取一个变量的类型时,使用 typeoflet div = document.createElement('div');type B = typeof div
复制代码


interface 可以而 type 不行


  • interface 能够声明合并

interface User {  name: string  age: number}
interface User { sex: string}
/*User 接口为 { name: string age: number sex: string }*/
复制代码


相关链接:

typescript 中的 interface 和 type 到底有什么区别?

TypeScript: Interfaces vs Types


3、never 关键词有什么用?


当有一个联合类型:


interface Foo {  type: 'foo'}
interface Bar { type: 'bar'}
type All = Foo | Bar
复制代码


在 switch 当中判断 type,TS 是可以收窄类型的 (discriminated union):


function handleValue(val: All) {  switch (val.type) {    case 'foo':      // 这里 val 被收窄为 Foo      break    case 'bar':      // val 在这里是 Bar      break    default:      // val 在这里是 never      const exhaustiveCheck: never = val      break  }}
复制代码


注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:


type All = Foo | Bar | Baz
复制代码


然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never,产生一个编译错误。所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。


参考链接:

尤雨溪:TypeScript中的never类型具体有什么用?


4、is 关键词有什么用?


先看一串代码:

function isString(test: any): boolean{  return typeof test === "string";}
function example(foo: any){ if(isString(foo)){ console.log("it is a string" + foo); console.log(foo.length); console.log(foo.hello()); }}example("hello world");
复制代码

上面的的代码不会有编译时错误,但是会有运行时错误。因为 字符串类型上没有 hello 方法。


这时候怎么优化,才能让 TS 在编译时就能检查到错误呢?


is 关键词可以派上用场,参考下面的代码:

function isString(test: any): test is string{  return typeof test === "string";}
function example(foo: any){ if(isString(foo)){ console.log("it is a string" + foo); console.log(foo.length); console.log(foo.hello()); // ts Error: 类型“string”上不存在属性“hello” }}
复制代码

我们把 boolean 替换成 test is string,那么当函数 isString 返回 true 时, TS 会将 test 的类型从 any 收缩到 string,从而能精确的进行类型判断。


相关链接:

What does the is keyword do in typescript?


5、infer 关键词有什么用?


infer 关键词常在条件类型中和 extends 关键词一同出现,表示将要推断的类型,作为类型变量可以在三元表达式的 True 部分引用。


例如,简单的类型提取:


type Unpacked<T> =  T extends (infer U)[] ? U : T;
type T0 = Unpacked<string[]>; // stringtype T1 = Unpacked<string>; // string
复制代码


上面提到的 ReturnType 工具类型 也是使用这种方式提取到了函数的返回类型:


type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
复制代码


相关链接:

TypeScript 的 Infer 关键词


6、TypeScript 有哪些实用工具类型?


`Partial<T>`


将 T 中所有属性转换为可选属性,返回的类型可以是 T 的任意子集。​这在需要支持接受部分属性的场景下非常有用:


interface Todo {  title: string;  description: string;  done: boolean;}
function updateTodo(todo: Todo, newTodo: Partial<Todo>) { return { ...todo, ...newTodo };}
const todo: Todo = { title: 'First Todo', description: 'this is the first todo', done: false};
updateTodo(todo, { done: true });
复制代码


源码:

type Partial<T> = { [P in keyof T]?: T[P]; };
复制代码


`Pick<T,K>`


通过在 T 中抽取一组属性 K 构建一个新类型:

interface Todo {  title: string;  description: string;  done: boolean;}
type TodoBase = Pick<Todo, 'title' | 'done'>;
const todo: TodoBase = { title: 'First Todo', done: false};
复制代码


源码:

// K是T的属性集合的子集type Pick<T, K extends keyof T> = { [p in K]: T[p] };
复制代码


`ReturnType<T>`


返回 function 的返回值类型:

type T0 = ReturnType<() => string>;  // string
type T1 = ReturnType<() => Promise<number>>; // Promise<number>
type T2 = ReturnType<(s: string) => void>; // void
复制代码


源码:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
复制代码


相关链接:

TypeScript Utility Types 源码解析


7、如何处理才能在 TS 中引用 CSS 或者 图片使之不报错?


例如,在下面的 TS 文件中,直接导入 .scss 文件或者 .png文件,TS 的静态检查会出错:


import S from "./index.scss"; // TS Error: 找不到模块“./index.scss”或其相应的类型声明import imgLogo from "./logo.png"; // // TS Error: 找不到模块“./logo.png”或其相应的类型声明
复制代码


这时候需要编写 TS 申明文件,可以在项目根目录下添加一个 index.d.ts 文件,其中写入:


declare module '*.png';
declare module '*.module.scss' { const classes: { [key: string]: string }; export default classes;}
复制代码


8、tsconfig.json 熟悉吗?有哪些常用属性?


一般的 tsconfg.json 可以如下配置:


{  "compilerOptions": {    "outDir": "./dist/",    "sourceMap": true,    "allowSyntheticDefaultImports": true,    "skipLibCheck": true,    "moduleResolution": "node",    "module": "esnext",    "target": "es5",    "noImplicitAny": false,    "jsx": "react",    "allowJs": true,    "baseUrl": "./",    "paths": {      "@utils/*": [        "src/utils/*"      ],      "@containers/*": [        "src/containers/*"      ],      "@components/*": [        "src/components/*"      ],    }  }}
复制代码


其中有一些常用的属性,例如:


1、allowJs: true


允许导入 .js 文件


2、paths


设置别名


3、allowSyntheticDefaultImports:true


// 这样不会报错import React, { Component} from 'react';// 要不然只能import * as React from 'react';
复制代码


4、moduleResolution:"node"


//可以直接引用文件夹import Head from '@components/head';// 而不需要import Head from '@components/head/index';
复制代码


9、TS 的装饰器有没有用过?试着写一个用于计算函数执行时间的的装饰器


/** * 装饰器方法 , 用于计算运行时间 * * @param target 目标对象 * @param propertyName 属性名 * @param propertyDescriptor 属性描述器 */export function runtime(  target: Object,  propertyName: string,  propertyDescriptor: PropertyDescriptor): PropertyDescriptor {  const method = propertyDescriptor.value;  propertyDescriptor.value = function(...args) {    console.time(propertyName);    const result = method.apply(this, args);    console.timeEnd(propertyName);    return result;  };  return propertyDescriptor;}
复制代码


10、一道 LeetCode 招聘面试题


下面是一道 LeetCode 面试题


// 假设有一个这样的类型:interface initInterface {  count: number;  message: string;  asyncMethod(input: Promise<string>): Promise<Action<number>>;  syncMethod(action: Action<string>): Action<number>;}
// 在经过 Connect 函数之后,返回值类型为
interface Result { asyncMethod(input: string): Action<number>; syncMethod(action: string): Action<number>;}
// 其中 Action<T> 的定义为:interface Action<T> { payload?: T type: string}// 现在要求写出Connect的函数类型定义。
复制代码


答案:


type RemoveNonFunctionProps<T> = {  [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];
type PickFunction<T> = Pick<T, RemoveNonFunctionProps<T>>;
type TransformMethod<T> = T extends ( input: Promise<infer U>) => Promise<Action<infer S>> ? (input: U) => Action<S> : T extends (action: Action<infer U>) => Action<infer S> ? (action: U) => Action<S> : never;
type ConnectAll<T> = { [K in keyof T]: TransformMethod<T[K]>;};
type Connect<T> = ConnectAll<PickFunction<T>>;
复制代码


参考链接:

不能不掌握的ts高级特性


**觉得不错可以 star 这个 repo 关注更多内容。**


用户头像

局外人

关注

还未添加个人签名 2017.12.12 加入

还未添加个人简介

评论

发布
暂无评论
前端 10 问之 TypeScript (第一篇)