德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)

网站地图

搜索
德胜云咨询
前端分类 javascript CSS 正则表达式 html 前端框架 typescript Ajax
热门标签:
最新标签:

typescript感叹号TypeScript 常见问题整理(60多个)「下」typescript react学会了吗,

日期:2023/03/27 07:21作者:邓诗涵人气:

导读:作者: 秋天不落叶转发链接:https://mp.weixin.qq.com/s/KmDLcJ0jhB667ZouDB8tyg前言用 React...

作者: 秋天不落叶

转发链接:

https://mp.weixin.qq.com/s/KmDLcJ0jhB667ZouDB8tyg

前言

用 React 全家桶 + TS 写项目快一年了,大大小小的坑踩了很多,在此整理了在项目中遇到的疑惑和问题。体会:不要畏惧 TS,别看 TS 官方文档内容很多,其实在项目中常用的都是比较基础的东西,像泛型运用、一些高级类型这种用的很少(封装库、工具函数、UI组件时用的比较多)。只要把常用的东西看熟,最多一个小时就能上手 TS。如果本文对你有所帮助,还请点个赞,谢谢啦~~

上一篇TypeScript 常见问题整理(60多个)「上」 讲了纯 TS 常见问题1-20个,接下来我们继续看从21题看。

21. typeof class 和直接用 class 作为类型有什么区别

class Greeter {     static message = hello;     greet(){         return Greeter.message;     } } // 获取的是实例的类型,该类型可以获取实例对象上的属性/方法 let greeter1:Greeter = new Greeter(); console.log(greeter1.greet());// hello // 获取的是类的类型,该类型可以获取类上面的静态属性/方法 let greeterTwo:typeof Greeter = Greeter; greeterTwo.message = hey; let greeter2:Greeter = new greeterTwo(); console.log(greeter2.greet());// hey

22. TS 中的 never 类型具体有什么用?

23. 当使用联合类型时,在类型未确定的情况下,默认只会从中获取共有的部分

使用类型断言interface Bird {     fly()     layEggs() } interface Fish {     swim()     layEggs() } function getSmallPet():Fish | Bird{     return ; } let pet = getSmallPet(); pet.layEggs(); // 当使用联合类型时,在类型未确定的情况下,默认只会从中获取共有的部分 // 需要使用类型断言 (pet as Fish).swim(); pet.swim();

image.png

可区分的联合类型(借助 never )enum KindType{     square = square,     rectangle = rectangle,     circle = circle, } interface Square {     kind: KindType.square;     size: number; } interface Rectangle {     kind: KindType.rectangle;     width: number;     height: number; } interface Circle {     kind: KindType.circle;     radius: number; } type Shape = Square | Rectangle | Circle; function area1(s: Shape{     // 如果联合类型中的多个类型,拥有共有的属性,那么就可以凭借这个属性来创建不同的类型保护区块     // 这里 kind 是共有的属性     switch (s.kind) {         case KindType.square:             return s.size * s.size;         case KindType.rectangle:             return s.height * s.width;         default:             return;     } } // 以上代码有隐患,如果后续新增类型时,TS 检查以上代码时,虽然缺失后续新增的类型,但不会报错 console.log(area1({kind: KindType.circle, radius: 1})); function area2(s: Shape{     switch (s.kind) {         case KindType.square:             return s.size * s.size;         case KindType.rectangle:             return s.height * s.width;         case KindType.circle:             return Math.PI * s.radius ** 2;         default:             // 检查 s 是否是 never 类型             // 如果是 never 类型,那么上面的分支语句都被覆盖了,就永远都不会走到当前分支             // 如果不是 never 类型。就说明前面的分支语句有遗漏,需要补上             return ((e: never) => {                 throw new Error(e)             })(s)     } } console.log(area2({kind: KindType.circle, radius: 1}));

24. Whats on version 3.3.3333?

25. 在全局环境中,不能给某些变量声明类型

let name: string; // 加了 export 后就不会报错 // export {} 

image.png

26. 不必要的命名空间:命名空间和模块不要混在一起使用,不要在一个模块中使用命名空间,命名空间要在一个全局的环境中使用

你可能会写出下面这样的代码:将命名空间导出

shapes.tsexport namespace Shapes {     export class Triangle { /* ... */ }     export class Square { /* ... */ } }shapeConsumer.tsimport * as shapes from "./shapes"; let t = new shapes.Shapes.Triangle();

不应该在模块中使用命名空间或者说将命名空间导出: 使用命名空间是为了提供逻辑分组和避免命名冲突,模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。

下面是改进的例子:

shapes.tsexport class Triangle { /* ... */ } export class Square { /* ... */ }shapeConsumer.tsimport * as shapes from "./shapes"; let t = new shapes.Triangle();

或者

shapes.ts namespace Shapes {     export class Triangle { /* ... */ }     export class Square { /* ... */ } }shapeConsumer.tslet t = new Shapes.Triangle();

27. 扩展全局变量的类型

interface String {     // 这里是扩展,不是覆盖,所以放心使用     double(): string; } String.prototype.double = function () {     return this + + + this; }; console.log(hello.double()); // 如果加了这个,就会报错 // export {}interface Window {     myname: string } // 注意:这里的 window 要小写 console.log(window); // 如果加了这个,当前模块就会变成局部的 // 然后定义的类型 Window 就是局部的变量,不再是一个全局变量 // 所以上面给 Window 扩展属性/方法就失效了 export {}

28. export = xxx 和 import xxx = require(xxx)

CommonJS 和 AMD 的环境里都有一个 exports 变量,这个变量包含了一个模块的所有导出内容。CommonJS 和 AMD 的 exports 都可以被赋值为一个对象, 这种情况下其作用就类似于 es6 语法里的默认导出,即 export default 语法了。虽然作用相似,但是 export default 语法并不能兼容 CommonJS和 AMD 的 exports。如果一个模块遵循 ES6 模块规范,当默认导出内容时(export default xxx),ES6 模块系统会自动给当前模块的顶层对象加上一个 default 属性,指向导出的内容。当一个 ES6 模块引入该模块时(import moduleName from xxx),ES6 模块系统默认会自动去该模块中的顶层对象上查找 default 属性并将值赋值给 moduleName。而如果一个非 ES6 规范的模块引入 ES6 模块直接使用时(var moduleName = require(xxx)),就会报错,可以通过 moduleName.default 来使用。为了支持 CommonJS 和 AMD 的 exports,TypeScript 提供了 export = 语法。export = 语法定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举。若使用 export = 导出一个模块,则必须使用 TypeScript 的特定语法 import module = require("module") 来导入此模块// exports === module.exports // 即:这两个变量共用一个内存地址 // 整体导出 // module.exports = {} // 导出多个变量 exports.c = 3; exports.d = 4;一个 es6 模块默认导出,被一个 node 模块导入使用// 兼容性写法只在 TS 中有效 !!!!!! // 兼容性写法只在 TS 中有效 !!!!!! // 兼容性写法只在 TS 中有效 !!!!!! // a.es6.ts // 这里只能导出一个 export = function () {     console.log("Im default") } // b.node.ts import fn = require(./a.es6.ts); fn();

29. 如何在 Node 中使用 TS

安装相关声明文件,如:@types/node;因为 node 模块遵循 CommonJS 规范,一些 node 模块(如:express)的声明文件,用 export = xxx 导出模块声明。TS 进行类型推导时,会无法推断导致报错。所以需要使用 import xxx from "xxx" 或者 import xxx = "xxx" 导入 node 模块;

30. 使用 as 替代尖括号表示类型断言

在 TS 可以使用尖括号来表示类型断言,但是在结合 JSX 的语法时将带来解析上的困难。因此,TS 在 .tsx 文件里禁用了使用尖括号的类型断言。as 操作符在 .ts 文件和 .tsx 文件里都可用interface Person {     name: string;     age: number } let p1 = {age: 18as Person; console.log(p1.name); // 这种写法在 .tsx 文件中会报错 let p2 = <Person>{age: 18}; console.log(p2.name);

31. 如何对 JS 文件进行类型检查

在 tsconfig.json 中可以设置 checkJs:true,对 .js 文件进行类型检查和错误提示。通过在 .js 文件顶部添加 // @ts-nocheck 注释,让编译器忽略当前文件的类型检查。相反,你可以通过不设置 checkJs:true 并在 .js 文件顶部添加一个 // @ts-check 注释,让编译器检查当前文件。也可以在 tsconfig.json 高配置 include/exclude,选择/排除对某些文件进行类型检查 。你还可以使用 // @ts-ignore 来忽略本行的错误。在 .js 文件里,类型可以和在 .ts 文件里一样被推断出来。当类型不能被推断时,可以通过 JSDoc 来指定类型。/** @type {number} */ var x; x = 0;      // OK x = false;  // Error: boolean is not assignable to numberTS 中支持的 JSDoc 注解

32. 不要使用如下类型 Number,String,Boolean、Object,应该使用类型number、string、boolean、object

/* 错误 */ function reverse(s: String)String; /* OK */ function reverse(s: string)string;

33. 如何在解构一个函数 `function fn({ x: number }) { /* … */ }` 时,即能给变量声明类型,又能给变量设置默认值

// error function f({ x: number }{     console.log(x); } // ok function f({x}: { x: number } = {x: 0}{     console.log(x); }

34. Pick摘取返回的结果是一个对象(或者说新的接口),里面包含摘取到的属性

interface Test {     arr: string[] } // pick 摘取返回的结果 => {arr: string[]} let aaa: Pick<Test, arr> = {arr: [1]};

35. 无法使用 for of 遍历 map 数据

const map = new Map([   [Fno],   [T,  yes], ]); for (let key of map.keys()) {   console.log(key); } // 用 forEach 也可以遍历 map.forEach((value,key) => {  console.log(key); });设置 target=es5 的时候,会报错误,并且无法执行 for 语句

TS2569: Type Map is not an array type or a string type. Use compiler. option - downlevellteration to allow iterating of iterators.

配置 dom.iterable 和 downlevelIteration 就可以正常运行tsconfig.json

{     /*当目标是ES5或ES3的时候提供对for-of、扩展运算符和解构赋值中对于迭代器的完整支持*/     "downlevelIteration"true,   "lib": [     "dom",     "es5",     "es6",     "es7",     "dom.iterable"   ] }设置 target=es6 的时候,就能正常执行。原因:

注意:如果未指定--lib,则会注入默认的库列表。注入的默认库是:► For --target ES5: DOM,ES5,ScriptHost► For --target ES6: DOM,ES6,DOM.Iterable,ScriptHost

36. 有时候我们需要复用一个类型,但是又不需要此类型内的全部属性,因此需要剔除某些属性

这个方法在 React 中经常用到,当父组件通过 props 向下传递数据的时候,通常需要复用父组件的 props 类型,但是又需要剔除一些无用的类型。interface User {     username: string     id: number     token: string     avatar: string     role: string } type UserWithoutToken = Omit<User, token>

37. 为什么在 exclude 列表里的模块还会被编译器使用

有时候是被 tsconfig.json 自动加入的,如果编译器识别出一个文件是模块导入目标,它就会加到编译列表里,不管它是否被排除了。因此,要从编译列表中排除一个文件,你需要在排除它的同时,还要排除所有对它进行 import 或使用了 ///指令的文件。

38. 使用 import xxx= namespace.xxx 创建命名空间别名

// a.ts namespace Shape {     const pi = Math.PI;     export function cricle(r: number{         return pi * r ** 2     } } // b.ts // 直接使用 // console.log(Shape.cricle(2)); // 或者通过以下方式来使用该命名空间中的变量/函数/类 // import newName = a.b.c.d 用来给常用的、层级较深的对象起一个短的名字 // 这里的 import 的作用是创建一个别名,为任意标识符创建别名,包括导入的模块中的对象 // 不要与用来加载模块的 import x from "module-name" 语法弄混了 import cricle = Shape.cricle; console.log(cricle(2));  注意,这里并没有使用 require 关键字,而是直接使用导入符号的限定名赋值。 这与使用 var 相似,但它还适用于类型和导入的具有命名空间含义的符号。 重要的是,对于值来讲,import 会生成与原始符号不同的引用,所以改变别名的 var 值并不会影响原始变量的值。

tsconfig.json 常用配置项注释

{     "compilerOptions": {         /**************基础配置**************/         /**************基础配置**************/         /**************基础配置**************/         /* 开启增量编译:TS 编译器在第一次编译的时候,会生成一个存储编译信息的文件,下一次编译的时候,会根据这个文件进行增量的编译,以此提高 TS 的编译速度 */         // "incremental": true,         /* 指定存储增量编译信息的文件位置 */         // "tsBuildInfoFile": "./",         /* 打印诊断信息 */         // "diagnostics": true,         /* 打印输出的文件 */         // "listEmittedFiles": true,         /* 打印编译的文件(包括引用的声明文件)*/         // "listFiles": true,         /* 指定 ECMAScript 的目标版本: ES3 (default), ES5, ES2015, ES2016, ES2017, ES2018, ES2019 or ESNEXT. */         // "target": "es5",         /* 指定模块代码的生成方式: none, commonjs, amd, system, umd, es2015, or ESNext. */         // "module": "commonjs",         /* 指定要包含在编译中的库文件——引用类库——即申明文件,如果输出的模块方式是 es5,就会默认引入 "dom","es5","scripthost"  */         /* 如果在 TS 中想要使用一些 ES6 以上版本的语法,就需要引入相关的类库 */         // "lib": [],         /* 允许编译 JS 文件 */         // "allowJs": true,         /* 检查 JS 文件*/         // "checkJs": true,         /* 指定 JSX 代码生成的模式: preserve, react-native, or react. */         /* react 模式下:TS 会直接把 jsx 编译成 js */         /* preserve 模式下:TS 不会把 jsx 编译成 js,会保留 jsx */         // "jsx": "preserve",         /**************声明文件相关配置**************/         /**************声明文件相关配置**************/         /**************声明文件相关配置**************/         /* 生成相应的类型声明文件 —— .d.ts */         // "declaration": true,         /* 声明文件的输出路径 */         // "declarationDir": "./d",         /* 只生成声明文件,不生成 JS */         // "emitDeclarationOnly": true,         /* 声明文件目录,默认 node_modules/@types */         // "typeRoots": [],         /* 要导入的声明文件包,默认导入上面声明文件目录下的所有声明文件 */         // "types": [],         /* 将多个相互依赖的文件合并并且把编译后的内容输出到一个文件里          * 可以用在产出 AMD 模块的场景中          * "module":"amd" 时,当一个模块引入了另外一个模块,编译的时候会把这两个模块的编译结果合并到一个文件中          */         // "outFile": "./",         /* 指定编译文件的输出目录 */         // "outDir": "./out",         /* 指定输入文件的根目录,用于控制输出目录的结构 */         // "rootDir": "./",         /* 启用项目编译 */         // "composite": true,         /*  输出的时候移除注释 */         // "removeComments": true,         /* 不输出文件 */         // "noEmit": true,         /* 发生错误时不输出文件 */         // "noEmitOnError": true,         /* 不生成 helper 函数,以前的话设置为 true 后,需要额外安装 ts-helpers */         /* 类似于 babel ,会给每个文件都生成 helper 函数,会使得最终编译后的包的体积变大 */         // "noEmitHelpers": true,         /* 现在可以通过 tslib(TS 内置的库)引入 helper 函数,!!!文件必须是模块 !!! */         /* 编译后自动引入 var tslib_1 = require("tslib") */         // "importHelpers": true,         /* 当目标是 ES5 或 ES3 的时候提供对 for-of、扩展运算符和解构赋值中对于迭代器的完整支持 */         // "downlevelIteration": true,         /* 把每一个文件转译成一个单独的模块 */         // "isolatedModules": true,         /**************严格检查配置**************/         /**************严格检查配置**************/         /**************严格检查配置**************/         /* 开启所有的严格检查配置 */         "strict": true,         /* 不允许使用隐式的 any 类型 */         // "noImplicitAny": true,         /* 不允许把 null、undefined 赋值给其他类型变量 */         // "strictNullChecks": true,         /* 不允许函数参数双向协变 */         // "strictFunctionTypes": true,         /* 使用 bind/call/apply 时,严格检查函数参数类型 */         // "strictBindCallApply": true,         /* 类的实例属性必须初始化 */         // "strictPropertyInitialization": true,         /* 不允许 this 有隐式的 any 类型,即 this 必须有明确的指向*/         // "noImplicitThis": true,         /* 在严格模式下解析并且向每个源文件中注入 "use strict" */         // "alwaysStrict": true,         /**************额外的语法检查配置,这种检查交给 eslint 就行,没必要配置**************/         /**************额外的语法检查配置,这种检查交给 eslint 就行,没必要配置**************/         /**************额外的语法检查配置,这种检查交给 eslint 就行,没必要配置**************/         /* 有未使用到的本地变量时报错 */         // "noUnusedLocals": true,         /* 有未使用到的函数参数时报错 */         // "noUnusedParameters": true,         /* 每个分支都要有返回值 */         // "noImplicitReturns": true,         /* 严格校验 switch-case 语法 */         // "noFallthroughCasesInSwitch": true,         /**************模块解析配置**************/         /**************模块解析配置**************/         /**************模块解析配置**************/         /* 指定模块的解析策略: node (Node.js) or classic (TypeScript pre-1.6)*/         /* 若未指定,那么在使用了 --module AMD | System | ES2015 时的默认值为 Classic,其它情况时则为 Node */         // "moduleResolution": "node",         /* 在解析非绝对路径模块名的时候的基准路径 */         // "baseUrl": "./",         /* 基于 baseUrl 的路径映射集合 */         // "paths": {},         /* 将多个目录放在一个虚拟目录下,用于运行时 */         /* 当自己编写的库和开发的代码都输出到一个目录下时,开发代码和库的位置不一样,开发代码引入库的路径就会不对 */         // "rootDirs": [],         // "rootDirs": ["src","out"],         /* 允许 export = xxx 导出 ,并使用 import xxx form "module-name" 导入*/         // "esModuleInterop": true,         /* 当模块没有默认导出的时候,允许被别的模块默认导入,这个在代码执行的时候没有作用,只是在类型检查的时候生效 */         // "allowSyntheticDefaultImports": true,         /* 不要 symlinks 解析的真正路径 */         // "preserveSymlinks": true,         /* 允许在模块中以全局变量的方式访问 UMD 模块内容 */         // "allowUmdGlobalAccess": true,         /************** Source Map 配置**************/         /************** Source Map 配置**************/         /************** Source Map 配置**************/         /* 指定 ts 文件位置 */         // "sourceRoot": "",         /* 指定 map 文件存放的位置 */         // "mapRoot": "",         /* 生成目标文件的 sourceMap */         // "sourceMap": true,         /* 将代码与sourcemaps生成到一个文件中,要求同时设置了--inlineSourceMap 或--sourceMap 属性*/         // "inlineSources": true,         /* 生成目标文件的 inline sourceMap —— 源文件和 sourcemap 文件在同一文件中,而不是把 map 文件放在一个单独的文件里*/         // "inlineSourceMap": true,         /* 生成声明文件的 sourceMap */         // "declarationMap": true,         /************** 实验性的配置**************/         /************** 实验性的配置**************/         /************** 实验性的配置**************/         /* 启用装饰器 */         // "experimentalDecorators": true,         // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */         /**************高级配置**************/         /**************高级配置**************/         /**************高级配置**************/         /* 强制区分大小写 */         // "forceConsistentCasingInFileNames": true }     /* 指定需要编译的单个文件列表 */     // "files": [],     /* 指定需要编译的文件/目录 */     // "include": [     //    // 只写一个目录名等价于 "./src/**/*"     //    "src"     //  ]     /* 需要排除的文件或目录 */     // "exclude": []     /* 配置文件继承 */     // "extends": "./tsconfig.base.json" }

tsconfig.json 配置项问题

1. 三种 JSX 模式

在 TS 中想要使用 JSX 必须做两件事:给文件一个 .tsx 扩展名启用 jsx 选项TS 具有三种 JSX 模式:preserve,react 和 react-native,这些模式只在代码生成阶段起作用,类型检查并不受影响。preserve 模式下: 不会将 JSX 编译成 JS,生成代码中会保留 JSX,以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有 .jsx 扩展名。react 模式下: 直接将 JSX 编译成 JS,会生成 React.createElement 的形式,在使用前不需要再进行转换操作了,输出文件的扩展名为 .js。react-native 模式下: 相当于 preserve,它也保留了所有的 JSX,但是输出文件的扩展名是 .js。

模式输入输出输出文件扩展名preserve<div /><div />.jsxreact<div />React.createElement("div").jsreact-native<div /><div />.js

2. "lib" 配置项需要注意的问题

当你安装 TypeScript 时,会顺带安装 lib.d.ts 等声明文件,此文件包含了 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。它自动包含在 TypeScript 项目的编译上下文中它能让你快速开始书写经过类型检查的 JavaScript 代码tsconfig.json 中的 lib 选项用来指定当前项目需要注入哪些声明库文件。如果没有指定,默认注入的库文件列表为:当 --target ES5:DOM,ES5,ScriptHost当 --target ES6:DOM,ES6,DOM.Iterable,ScriptHost如果在 TS 中想要使用一些 ES6 以上版本或者特殊的语法,就需要引入相关的类库。如:ES7 、DOM.Iterable

3. "moduleResolution" 解析策略

https://www.tslang.cn/docs/handbook/module-resolution.html

4. 指定 target 为 es6 时,tsc 就会默认使用 "classic" 模块解析策略,这个策略对于 `import * as abc from "@babel/types"` 这种非相对路径的导入,不能正确解析。

解决方法:指定解析策略为 node => "moduleResolution": "node"。

5. "esModuleInterop" 具体作用是什么

如果一个模块遵循 ES6 模块规范,当默认导出内容时(export default xxx),ES6 模块系统会自动给当前模块的顶层对象加上一个 default 属性,指向导出的内容。当一个 ES6 模块引入该模块时(import moduleName from xxx),ES6 模块系统默认会自动去该模块中的顶层对象上查找 default 属性并将值赋值给 moduleName。而如果一个非 ES6 规范的模块引入 ES6 模块直接使用时(var moduleName = require(xxx)),就会报错,需要通过 moduleName.default 来使用。TypeScript 为了兼容,引入了 esModuleInterop 选项,设置 esModuleInterop 为 true ,在编译时自动给该模块添加 default 属性,就可以通过 import moduleName from xxx 的形式导入 非 ES6 模块,不再需要使用 import moduleName = require(xxx) 的形式。

6. "allowSyntheticDefaultImports" 具体作用是什么

允许 默认导入 没有设置默认导出(export default xxx)的模块,可以以 import xxx from xxx 的形式来引入模块// 配置前 import * as React from react; import * as ReactDOM from react-dom; // 配置后 import React from react; import ReactDOM from react-dom;

7. "paths" 配置路径映射集合时,需要注意的问题

{    "paths": {       // 这里的路径后面必须跟着 "/*"       "@public/*": [         // 这里的路径后面必须跟着 "/*"         "public/*"       ],       "@src/*": [         "src/*"       ],       "@assets/*":[         "src/assets/*"       ],       "@components/*": [         "src/components/*"       ]     } }

8. "allowJs" 时需要注意的问题

设置 "allowJs": false :在 .ts / .tsx 文件中引入 .js / .jsx 文件时,就不会有相关提示

image.png

React + TS 项目问题

1. 使用 import 引入非 JS 模块会报错,而使用 require 则没有问题

import styles from ./login.less; import logo from @assets/images/logo.svg; const logo2 = require(@assets/images/logo.svg); console.log(logo2);// path

image.png

解决办法: 给这些非 JS 模块添加申明

/**  * style  */ declare module *.css declare module *.less // declare module "*.less" { //     const styles: { [className: string]: string }; //     export default styles // } declare module *.scss /**  * 图片  */ declare module *.svg declare module *.png declare module *.jpg declare module *.jpeg declare module *.gif declare module *.bmp

2. import * as React from react 和 import React from react 有什么区别

第一种写法是将所有用 export 导出的成员赋值给 React ,导入后用 React.xxx 访问第二种写法仅是将默认导出(export default)的内容赋值给 React

3. 解决 import * as xxx from xxx 这种奇怪的引入方式

配置 tsconfig.json{   // 允许 默认导入 没有设置默认导出(export default xxx)的模块   // 可以以 import xxx from xxx 的形式来引入模块     "allowSyntheticDefaultImports":true }// 配置前 import * as React from react; import * as ReactDOM from react-dom; // 配置后 import React from react; import ReactDOM from react-dom;

4. 对 antd 组件库进行按需加载

这里使用的是 ts-loader 转译 TS 方案,更多方案请看 Webpack 转译 Typescript 现有方案

.babelrc

{   "presets": [     "@babel/preset-react",     "@babel/preset-env"   ],   "plugins": [     [       "import",       {         "libraryName""antd",         "libraryDirectory""es",         "style""css"         /* `style: true` 会加载 less 文件*/       }     ]   ] }

tsconfig.json

{   "compilerOptions": {     "target""es5",     "jsx""preserve",// 保留 jsx      ... }

webpack.config.js

{   test: /\.tsx?$/,     use: [       babel-loader,       ts-loader     ] },

5. 声明通过 React.createRef()创建的 ref 类型

// 源码 // interface RefObject<T> { //     readonly current: T | null; // } const ref1:React.RefObject<HTMLDivElement> = React.createRef(); const inputRef = React.createRef<Comp>(); class EditScene extends React.Component<Props{     inputRef:React.RefObject<Comp>      constructor(props) {         super(props);           this.inputRef = React.createRef<Comp>();     } }

6. react + redux + react-redux 项目:使用 @connect 装饰器正常,但是一旦结合 TS 后,就会报错

https://segmentfault.com/a/1190000016047027

import {ComponentClass} from react import {     connect as nativeConnect,     MapDispatchToPropsParam,     MapStateToPropsParam } from react-redux import {withRouter as nativeWithRouter} from react-router-dom export type ComponentDecorator<P = any> = <T extends ComponentClass<P>>(WrappedComponent: T) => T export const connect: <P, S>(     mapState: MapStateToPropsParam<Partial<P>, P, S>,  // mapDispatch?: MapDispatchToPropsParam<Partial<P>, P>     mapDispatch?: any ) => ComponentDecorator = nativeConnect as any; export const withRouter: ComponentDecorator = nativeWithRouter as any;

7. react + redux + react-redux 项目:在使用 mapStateToProps(state) 函数时,想要给仓库中的 state 声明类型

借助 ReturnType// rootReducer.ts import {combineReducers} from redux; import {connectRouter} from connected-react-router; import history from ../history; import evidenceEdit from ./evidence; import common from ./common; import work from ./work; import setScene from ./set-scene; let reducers = {     common,     work,     setScene,     evidenceEdit,     router: connectRouter(history) }; // 使用 ReturnType 从 rootReducer 推断状态形状 // export type AppState = ReturnType<typeof rootReducer> export type AppState = {     [key in keyof typeof reducers]: ReturnType<typeof reducers[key]> } const rootReducer = combineReducers(reducers); export default rootReducer;// setScene 模块 import * as types from ../types/action-types; import {appEditAction} from ../actions/common; export interface SetSceneState {     loadSuccess: boolean;     loadProgress: number; } let initState: SetSceneState = {     loadSuccess: false,     loadProgress: 0, }; export default function (state: SetSceneState = initState, action: appEditAction{     switch (action.type) {         case types.SCENE_DATA_LOADSUCCESS: {             return {...state, loadSuccess: action.payload.success};         }         case types.SCENE_DATA_LOADINGPROGRESS: {             return {...state, loadProgress: action.payload.num};         }         default:             return state;     } }

使用

image.png

8. react + redux + react-redux 项目:想要给 action creator 函数声明类型

// 在 Mesh 组件中 import workActions from "@store/actions/work"; interface MeshProps {     // 刚开始我是这样写的,每次都得在组件的 Props 里重新声明一下函数     // updateSceneData?: (workId: string,data) => appEditAction;     updateData?: typeof workActions.updateData; } @connect(null, {     updateData: workActions.updateData, }) class Mesh extends React.Component<MeshProps> {...}// store/actions/work.ts import * as types from ../types/action-types; import {appEditAction} from "@edit-store/actions/common"; export default {     updateWorkData(workId: string, data: any): appEditAction {         return {type: types.UPDATE_WORK_ASYNC, payload: {workId, data}}     } }

9. react + redux + react-redux 项目:给 React 组件的 Props 声明类型(较为便捷的方法)

import * as React from react; import {RouteComponentProps} from react-router; import {connect} from "@store/connect"; import {AppState} from "@store/reducers"; import commonActions from "@store/actions/commonActions"; // 组件可能有四个属性来源 // 1.mapStateToProps 的返回值 // 2.actions 对象类型 // 3.来自路由 // 4.父组件传进来的其它属性 // 原先的写法:一个个拼起来,mapStateToProps 返回的状态还得在 Props 接口里再声明一遍,比较混乱、麻烦 // interface Props { //     loadProgress?: number; //     markVisible?: boolean; //     setMarkVisible?: typeof commonActions.setMarkVisible; // } function mapStateToProps(state: AppState{     const {markVisible,loadProgress} = state;     return {         markVisible,         loadProgress,     }; } // 现在的写法:便捷 type StateProps = ReturnType<typeof mapStateToProps>; type DispatchProps = typeof commonActions; interface IParams {} type RouteProps = RouteComponentProps<IParams>; type Props = StateProps & RouteProps & DispatchProps & {}; @connect(mapStateToProps, {     setMarkVisible: commonActions.setMarkVisible }) export default class App extends React.PureComponent<Propsany{     render() {         const {markVisible, loadProgress} = this.props;         return (<div > {markVisible} {loadProgress} </div>);     } }

10. react + redux + react-redux 项目:想要给 redux-thunk 声明类型

redux thunk 有一个内置类型 ThunkAction,我们可以这样使用:

// src/thunks.ts import { Action } from redux import { sendMessage } from ./store/chat/actions import { AppState } from ./store import { ThunkAction } from redux-thunk export const thunkSendMessage = (   message: string ): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {   const asyncResp = await exampleAPI()   dispatch(     sendMessage({       message,       user: asyncResp,       timestamp: new Date().getTime()     })   ) } function exampleAPI() {   return Promise.resolve(Async) }

11. 使用 webpack 的 module.hot 会警告没有类型定义

# 下载这个类型声明文件npm install --save @types/webpack-envif (process.env.NODE_ENV !== production) {     if (module.hot) {         module.hot.accept(./reducers() => store.replaceReducer(rootReducer));     } }

12. tsconfig-paths-webpack-plugin 这个包会将 tsconfig.json 中的 path 配置项内容映射到 webpack 配置中去,这样就不需要在 webpack 中的 alias 配置项里配置路径映射

image.png

13. react 函数组件声明

interface Greeting {     name: string;     age: number; } const Hello:React.FC<Greeting> = (props) => <h1>Hello {props.name}</h1>; // 推荐使用第二种 const Hello2 = (props:Greeting) => <h1>Hello {props.name}</h1>;

14. 如何编写 react + ts 版的 HOC

import React, { Component } from react; import HelloClass from ./HelloClass; interface Loading {     loading: boolean } // HOC 可以接收一个类组件,也可以接收一个函数组件,所以参数的类型是 React.ComponentType // 源码:type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>; function HelloHOC<P>(WrappedComponent: React.ComponentType<P>{     return class extends Component<P & Loading{         render() {             const { loading, ...props } = this.props;             return loading ? <div>Loading...</div> : <WrappedComponent { ...props as P } />;         }     } } export default HelloHOC(HelloClass);

15. 快速获取事件处理函数的 event 参数类型

 class Login extends React.Component <Props>{     handlerLinkBtnClick = (ev) => {           console.log(ev);         this.props.historyGo(./register);     };     handlerLinkBtnMouseMove = (ev) => {        console.log(ev);     };     render() {         return (             <div>                 <header>                     <p >This is Login Page </p>                     <div className={styles.linkBtn}                          onMouseMove={this.handlerLinkBtnMouseMove}                           onClick={this.handlerLinkBtnClick}>                          Go to Register Page                    </div>                 </header>             </div>         );     } }

按住 Ctrl ,然后鼠标移动到事件名上就能获取当前事件处理函数的参数类型

本篇文章已完结

推荐TypeScript知识点文章

TypeScript 常见问题整理(60多个)「上」

深入TypeScript难点梳理讲解

Vue3.0之前你必须知道的TypeScript实战技巧

深入TypeScript难点梳理讲解

Vue3.0之前你必须知道的TypeScript实战技巧

你需要的 React + TypeScript 50 条规范和经验

TypeScript详细概括【思维导图】

Vue3.0 尝鲜 Hook TypeScript 取代 Vuex【项目实践】

TypeScript详细概括【思维导图】

「新消息」基于JavaScript/TypeScript 编程环境Deno1.0 即将发布

「干货」一张页面引起的项目架构思考(rax+Typescript+hooks)

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

作者: 秋天不落叶

转发链接:

https://mp.weixin.qq.com/s/KmDLcJ0jhB667ZouDB8tyg

排行

网站地图

Copyright © 2002-2022 香港德胜云网络 版权所有 | 备案号:蜀ICP备2023007363号-5

声明: 本站内容全部来自互联网,非盈利性网站仅供学习交流