前言
本人TS菜鸟一枚,在项目中,使用ts的过程中,踩过许多坑,有的问题记录下来了解决方案,有的疏漏了。现将记录下来的一些TS问题的解决方案给大家分享一下,希望大家能在工作中少踩点TS的坑,每天都能到点下班,带着愉快的心情回家。
30. interface和type的区别?
它们都可以描述一个对象或者函数,区别是:
- type 可以声明基本类型别名,联合类型,元组等类型
- type 可以使用 typeof 获取实例类型进行赋值
- interface 能够声明合并
- interface扩展类型语法是 extends,type扩展类型语法是 & (交叉)
29. setTimeout类型的定义
ts
const timer:Ref<ReturnType<typeof setTimeout>> = ref(null);
28. tsconfig.json的path别名设置,有可能会被覆盖。造成所有别名路径都报警告。模块不存在。不要在子应用中的tsconfig.json中重新定义path字段。
27.includes方法参数报内容不能赋值给never类型,解决方法:将前面的值转换成数组类型
ts
selectedRowKeys.value?.includes(record?.[props.rowKey] as [])
26. 动态方法名不会触发ts告警的正确写法
ts
// 错误写法
// status 0启用 1禁用
let apiName = status === 0 ? 'disabledProtocol' : 'enableProtocol';
(agreementApi[apiName as any] as any)({ protocolId })
// ...
// 正确写法
agreementApi[status === 0 ? 'disabledProtocol' : 'enableProtocol']({ protocolId })
// ...
25. 给一个对象初始化赋值为{}或者一个数组初始化赋值为[ ]引起的ts告警如何解决?
参考如下案例:
ts
interface Foo {
bar: number;
bas: string;
}
let foo = {} as Foo;
foo.bar = 123;
foo.bas = 'Hello World';
24. props某个属性有多种类型定义方式
ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
// 定义具体的函数签名有助于更好的类型推断
type ApiFunction = () => number;
export default defineComponent({
props: {
apiAction: [String, Function] as PropType<string | ApiFunction>,
default: ''
}
})
23. 常用的几种操作
- 限定方法参数名类型
ts
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] { return o[name] }
- 取出item[]中的item类型
ts
type IItem=ActivityListResponse['data'][0]
- 覆盖原有类型
ts
type Modify<T, R> = Omit<T, keyof R> & R;
22. ts中 object VS Object VS {}的区别
这三者的区别是:
- 原型上属性方法重写表现不一致 Object不能重写原型上的方法, {}和object可以。
- object类型值表示⾮原始类型,Object,{}类型值可以为原始类型 number|string:boolean;
看下面的实验:
- Object
ts
let obj: Object; // 或者 let obj = new Object();
obj = "hell oworld";
obj = 1;
obj = true;
obj = undefined; //Error:Type 'undefined' is not assignable to type 'Object'.
obj = {
// Type 'number' is not assignable to type 'string'
toString() {
return 123;
}
}
- {}
ts
let obj: {};
obj = undefined; //Error:Type 'undefined' is not assignable to type '{}'.
obj = 'a';
obj = {
a : "hell oworld",
b : 1,
c : true,
toString() {
return 123;
}
}
console.log(obj)
/*
{
"a": "hell oworld",
"b": 1,
"c": true
}
*/
console.log(obj.toString()) // 123;
- object
ts
let obj: object;
obj = 1; // Error:Type 'number' is not assignable to type 'object'.
obj = true; // Error: Type 'boolean' is not assignable to type 'object'.
obj = 'a'; // Error: Type 'string' is not assignable to type 'object'.
obj = undefined; //Error:Type 'undefined' is not assignable to type 'object'.
obj = {
a : "hell oworld",
b : 1,
c : true,
}
obj = {
toString() {
return 123;
}
}
console.log(obj.toString()) // 123
综上所述,object才是我们想要的定义一个空对象所需的类型。
21. 索引报错是个烦恼而不是助力
对象的key使用变量索引时,VSCode会报标示红色的波浪线,报:元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{}"。\在类型 "{}" 上找不到具有类型为 "string" 的参数的索引签名。ts(7053)的解决方法。
ts
const person = {
name: '张三',
age: 10
};
function getValue(arg: string) {
return person[arg];
}
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ isMobile: string; isUnion: string; isTelcom: string; no: s
tring; }'
解决方法:在tsconfig.json中配置
json
{
"compilerOptions": {
"suppressImplicitAnyIndexErrors":true,
},
}
20. 组件已引入并可以使用,ts却提示找不到组件模块,如何解决?
加一条类型声明即可
ts
declare module '@sensetime/razor';
19. 交叉|联合类型
交叉类型--定义的变量中要有多种类型中的所有属性,联合类型--定义的变量要符合多种类型的某一种。
交叉类型 有同名属性时,类型要同时满足多种类型中的同名字段类型。比如leftType中id的类型是0|1,RightType中id的类型是number,那么交叉类型中的id,就要满足0|1,又要满足number
联合类型 有同名属性时,类型要同时满足多种类型中某一种类型的同名字段类型。比如leftType中left的类型是'0'|'1',RightType中left的类型是string那么联合类型中的left,要么满足'0'|'1',要么满足string
ts
interface LeftType {
id: 0 | 1;
left: '0' | '1';
}
interface RightType {
id: number;
left: string;
}
type IntersectionType = LeftType & RightType;
const t: IntersectionType = {
id: 0,
left: '1',
};
type UnionType = LeftType | RightType;
const t: IntersectionType = {
id: 4,
left: '4',
};
18. react-router history方法不存在的解决方法
bash
# 注意node的版本必须大于v12
yarn add @types/react-router @types/history -D
方法一:
ts
import * as H from 'history';
interface IProps {
history: H.History;
}
方法二:
ts
import { RouteComponentProps } from 'react-router';
// 声明全局变量
declare global {
type history = RouteComponentProps['history'];
}
17. react ref类型定义方法
html元素类型定义
js
class TestApp extends React.Component<AppProps, AppState> {
private stepInput: React.RefObject<HTMLInputElement>;
constructor(props) {
super(props);
this.stepInput = React.createRef();
}
render() {
return <input type="text" ref={this.stepInput} />;
}
}
组件类型定义
ts
import React, { createRef, RefObject } from 'react';
import { RankArea } from '@components/marketAnalyse';
// 浏览产品+开售提醒+前往办理 客户数排名组件引用
private rankAreaRef: RefObject<RankArea> = createRef();
<RankArea ref={this.rankAreaRef} data={responseMap} />
16.tsconfig.json配置字段含义
json
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}
- typeRoots 和 types 区别
typeRoots 和 types 这两个选项默认只对通过 npm 安装的声明模块有效,用户自定义的类型声明文件与它们没有任何关系。
默认的,所有位于 node_modules/@types 路径下的模块都会引入到编译器。相当于如下配置
json
{
"typeRoots" : ["node_modules/@types"]
}
具体来说是,./node_modules/@types 、../node_modules/@types、../../node_modules/@types 等等。
如果不希望自动引入 typeRoots 指定路径下的所有声明模块,那可以使用 types 指定自动引入哪些模块。
json
{
"types": ["lodash-es"],
}
禁止自动引入的话,配置为
json
{
"types":[]
}
自定义声明文件的引入方法:
15. ts中vue文件不被识别
解决方案: 创建一个vue.d.ts的文件
ts
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
14.类型"ImportMeta"上不存在属性"env"。
解决方法: 在typings下新建一个env.d.ts文件
ts
interface ImportMeta {
env: Record<string, unknown>
}
13. process.env报未定义的解决方法
bash
yarn add @types/node
在项目下的tsconfig.json中添加如下配置
json
{
"compilerOptions": {
// ...
"typeRoots": ["node_modules/@types"], // 要包含的类型声明文件名列表
}
}
12. 给div增加自定义属性的方法
可以扩展React命名空间,自定义一个react.d.ts文件,消除div上书写name属性报错的问题
ts
declare module 'react' {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
// 扩展 React's HTMLAttributes
name?: string;
}
}
11. Class Component this上不存在xxx静态属性的声明方法的解决方法
ts
class Foo extends React.Component{
share:string[]='hello'
}
10. 不能将xxx类型赋值给xxx类型的解决方法:用as 断言
ts
interface ICustomShareProps {
current:unkown;
[key: string]?: unknown;
}
const customShareRef = useRef<ICustomShareProps>({});
<CustomShare
ref={(el) => (customShareRef.current = el as ICustomShareProps)}
sensorsEvent={props.sensorsEvent}
shareObj={shareObj}
history={props.history}
hideSomeShareOption={true}
/>
9.不能将类型void分配给类型ReactElement<any,any> | null,的解决方法:
修改react函数组件返回值
js
const App: React.FC<any> = (): ReactElement<any, any> | null => {
// ...
}
8. 对象可能为null, 如何解决?
解决方法:
- 用可选链 ?.
- 在后面加个!,非常肯定地告诉ts编译器,对象一定存在
ts
// 改法1
document.getElementById('share-box-bg')?.addEventListener
// 改法2
document.getElementById('share-box-bg')!.addEventListener
7. 如何在Typescript中定义Promise的返回值类型?
- 方法一 通过
Promise
的构造函数,声明返回值的泛型类型
ts
const foo = new Promise<{code:number;msg:string}>((reslove, reject) => {
reslove({code:0,msg:'ok'});
})
- 方法二 定义
reslove
的类型
ts
const foo = new Promise((reslove:(value:{code:number;msg:string})=>void, reject) => {
reslove({code:0,msg:'ok'});
})
6. 找不到名称wx,WeixinJSBridge和Document上不存在属性attachEvent的解决方法
在./src/typings/global.d.ts中定义如下内容:
ts
// 声明全局变量
declare global {
interface Window {
$log: any;
$error: any;
}
interface Document {
attachEvent(event: string, listener: EventListener): boolean;
detachEvent(event: string, listener: EventListener): void;
}
namespace WeixinJSBridge {
function call(apiName: string): void;
}
namespace wx {
function error(config: unknown): void;
function config(config: unknown): void;
function checkJsApi(config: unknown): void;
function ready(config: unknown): void;
function agentConfig(config: unknown): void;
function onMenuShareTimeline(config: unknown): void;
function onMenuShareAppMessage(config: unknown): void;
function updateAppMessageShareData(config: unknown): void;
function updateTimelineShareData(config: unknown): void;
function invoke(apiName: string, config: object, cb: unknown): void;
function hideMenuItems(config: unknown): void;
function showMenuItems(config: unknown): void;
function hideOptionMenu(): void;
function getLocation(config: unknown): void;
}
}
// esModule要求必须有导出项,全局声明实际上不必导出,这样写是为了避免编译器提示警告
export {};
然后在tsconfig.json中引入全局类型声明
ts
{
"include": ["./src/typings"]
}
5.全局类型声明无法通过eslint校验的处理方法,添加到globals声明中去。
js
module.exports = {
// ...
globals: {
"wx": "readonly",
"WeixinJSBridge": "readonly"
},
}
4. e.target.value不存在,解决方案
ts
const foo=(e:ChangeEvent<HTMLInputElement>)=>{ console.log(e.target.value)}
其它事件类型还有这些:
ts
AnimationEvent
ChangeEvent
ClipboardEvent
CompositionEvent
DragEvent
FormEvent
KeyboardEvent
MouseEvent
PointerEvent
TouchEvent
TransitionEvent
WheelEvent
3.禁用ts类型检查
有些ts报错处理起来既繁琐,意义也不大,这时候就可以考虑屏蔽一些ts错误
ts
/*===忽略整个文件===*/
// @ts-nocheck
/*===忽略当行===*/
// @ts-ignore
2.枚举定义,使用场景-状态值
ts
// 枚举状态
enum TaskStatus {
Undo = 0,
Done = 1,
}
if(status === TaskStatus.Undo){
// do something
}
1.取出接口类型的keys和value类型值
ts
interface tabs {
待处理: 1;
已处理: 2;
}
// tab类型Label
type TTabLabel = keyof tabs;
type ValueOf<T> = T[keyof T];