typescript
语法
1、 a
标签添加clstag这种自定义标签类型错误问题
添加了declare namespace JSX
和declare module 'react'
不起作用,而且引入新的ts校验错误
按照html5标准建议,只要是自定义标签,都应使用data-
开头的定义
- 最终解决办法,将
<a clstag={`....`}>{children}</a>
改为如下格式,勉强骗过校验,达到不报错的地步。 正路还是应该寻找扩展HTMLAnchorElement属性的方法
const props = {
clstag: '...'
}
<a {...props}>{children}</a>
2、 关于mobx-react
的@inject
已注入属性,但在使用该组件时还是类型校验提示缺少属性的问题
-
参考 通过一个自定义
get injected
属性来避免直接获取props的方法,需要在每个组件中都多写一个方法 -
采用方案,在注入属性后使用
!
表明该属性确认存在。const { router } = this.props.store!
3、 在运行时通过扩展添加属性的对象,用&
例如在store/header
中使用了storeProp
export interface IHeader {
logoPlayed: boolean
setLogoPlayed: (v: any) => void
restoreLogoPlayed: () => void
}
@storeProp({
setter: [
{
default: !!storage.get('logoPlayed'),
name: 'logoPlayed',
},
],
})
class Header {
public headerProp = false
}
const header = new Header() as IHeader & Header
export { Header }
export default header
在使用header
时即可获取IHeader
与Header
中的所有属性
4、要求每个实例的属性都有初始值,初始值既可以在 constructor 中设置,也可以在声明时设置
Property 'fetchBatch' has no initializer and is not definitely assigned in the constructor.
如果一个普通类中的方法只是被定义了,而没有进行任何实现,便会报以上错误,解决办法之一是这样写:
public restoreBatch!: () => void
如果每次都这样写确实有些麻烦,所以可以在tsconfig.json
添加以下配置:
"strictPropertyInitialization": false,
但是,以更严谨的角度来说,未进行实现的任何方法都不应该写在普通类里,而应该放在抽象类里。因此,遇到以上情况时,代码应该以下方式来实现:
abstract class A{
public restoreBatch: () => void
}
class B extends A{}
const b = new B()
export default b
5、变量导出不能作为类型,即使这个变量是从一个class赋值而来
class A {}
export const A1 = A
// other file
import { A1 } from 'A'
// 此时A1仅仅作为一个变量使用,无法作为类型来使用的。
// 是个坑 🥵 !
6、高阶组件的children
类型定义
例如src/component/Permission
组件的children
类型应该定义为children: React.ReactNode
。在 React 中将ReactNode
的类型定义成了:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
7、antd 的Form.create()
修饰器
此修饰器目前在 ts 中无法使用,只能换成 function 的形式
例如:
@Form.create()
class Test{}
换成:
class Test{}
Form.create()(Test)
8、解决以下异常的两种方法
异常信息:
Property 'store' is missing in type '{}' but required in type 'Readonly<RcBaseFormProps & Pick<IProps, "store">>'.
Search.tsx
源码:
interface IProps {
store: {
rollbackEdit: RollbackEditStore
}
form: WrappedFormUtils
}
@inject('store')
@observer
class Search extends React.Component<IProps> {
public store: RollbackEditStore
constructor(props: IProps) {
super(props)
const {
store: { rollbackEdit },
} = this.props
this.store = rollbackEdit
}
public onChange = (value: SelectValue) => {
const { form } = this.props
this.store.setType(value)
this.store.setList([])
form.setFieldsValue({
id: undefined,
})
}
public render() {
...
}
}
export default Form.create()(Search)
第一种解决方法:
去掉constructor
中对 store 引用的定义。在IProps
的store
后面加一个可选符问号
interface IProps {
store?: {
rollbackEdit: RollbackEditStore
}
form: WrappedFormUtils
}
将方法中引用的this.store
都修改为const { rollbackEdit } = this.props.store!
第二种解决方法:
不修改Search.tsx
中的代码,只需要在调用它的index.tsx
中将 store 给它传入进去即可:
const store = {
rollbackEdit,
}
const RollbackEdit = () => (
<Card
title={
<CardTitle>
<Search store={store} />
</CardTitle>
}
bordered={false}
>
<List />
</Card>
)
9、typescript引起的webpack split chunk失效。
与ts官网等,得知tsconfig的
module: 'commonjs'
必然会导致代码分割实效,必须配置为
module: 'esnext'
而webpack.config.ts
中又使用了commonjs的包,ts文档中明确说明,使用
export = xxxx
导出的包,必须使用
import xxx = require('xxx')
的方式来引入。但只要有这种包的使用,就无法使用esnext
的模块类型。
可选方案:
- 使用
tsconfig-paths-webpack-plugin
,引入另一个tsconfig.json
文件,该文件使用esnext
的模块方式。 - 较简洁的方式,在
webpack
的ts-loader
中添加options
项,来达到使前端编译环境与源码环境使用不同tsconfig配置的目的。
options: {
compilerOptions: {
module: 'esnext',
},
},
10、函数类型的双变性
具体可查看:函数类型的双变性
11、类型守卫与类型区分
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
// 每一个成员访问都会报错
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
为了让这段代码工作,我们需要使用类型断言:
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
这里可以注意到我们不得不多次使用类型断言。 假若我们一旦检查过类型,就能在之后的每个分支里清楚地知道pet
的类型的话就好了。
TypeScript里的类型守卫机制让它成为了现实。 类型守卫就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型守卫,我们只要简单地定义一个函数,它的返回值是一个类型谓词:
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
在这个例子里,pet is Fish
就是类型谓词。 谓词为parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。
每当使用一些变量调用isFish
时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
12、css-module的类型检查
一开始绕了弯路,用typescript-plugin-css-modules
,对less支持不好,当使用复杂less函数时会失效,在更新less文件时还经常需要重启tsserver。
后来改进参考如下: typings-for-css-modules-loader
使用一个webpack loader
,即时生成对应的.d.ts
文件来映射css modules
的class
。
13、tsc命令行参数
只编译一个文件时,如果指定了tsc
参数,则不会自动加载当前项目的tsconfig.json
。这一点很迷惑,会生成错误的编译结果。
14、ts-loader
与fork-ts-checker-webpack-plugin
与awesome-typescript-loader
。
ts-loader
单独使用,无命令行类型校验提示,且项目越多编译越慢。
ts-loader
需要与fork-ts-checker-webpack-plugin
一起使用,可以做到文件更新后秒编译,在命令行显示类型校验,但消耗内存多。
awesome-typescript-loader
消耗内存稍少,文件更新时编译快,且可以显示命令行类型校验。例如更改的是依赖较多的文件例如被view依赖的更底层的store,编译速度会稍微慢。
15、typescript
与preact
与web component
。
首先需要在tsconfig.json中添加
"jsx": "react",
"jsxFactory": "h",
在preact中使用自定义dom时,需要扩展IntrinsicElements,google上能搜到的地方都让扩展global下的JSX
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
但在preact
环境下JSX所在的命名空间不同
declare global {
namespace preact {
namespace h {
namespace JSX {
interface IntrinsicElements {
'custom-node': any
}
}
}
}
}
配置
16、tsconfig.json
的路径别名path
如果只是当前项目使用,可以尽情用
在和其他项目一起使用时,我的情况是作为多包的lerna项目,一个包需要引入另一个包的ts文件,则原包中的路径别名在另一个包中是不能工作的。
17、.d.ts
与发布
项目发布时,即使打开了tsconfig.json
中的declaration
,其中的.d.ts
文件也不会自动编译到outDir
指定的目录中去
如果项目中有定义需要依赖这些.d.ts
,需要在发布过程中添加额外的复制脚本
.d.ts
中尽量不要使用import
引入其他依赖,尤其是不能引入非纯定义文件的类型,比如该文件中含有实际的逻辑代码,否则发布后在使用该包的环境中不能正常工作
18、使用babel编译
由于项目中有很多使用abstract定义的抽象类,在ts-loader编译时抽象类只作为类型检查使用。但使用babel编译时抽象类会生成真实的类,并且其中的属性也都会与类一起生成。在使用mobx扩展属性时造成冲突导致无法运行。