TypeScript 开发经验分享

3,770 阅读7分钟

typescript

语法

1、 a标签添加clstag这种自定义标签类型错误问题

添加了declare namespace JSXdeclare 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时即可获取IHeaderHeader中的所有属性

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 引用的定义。在IPropsstore后面加一个可选符问号

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的模块类型。

可选方案:

  1. 使用tsconfig-paths-webpack-plugin,引入另一个tsconfig.json文件,该文件使用esnext的模块方式。
  2. 较简洁的方式,在webpackts-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 modulesclass

13、tsc命令行参数

只编译一个文件时,如果指定了tsc参数,则不会自动加载当前项目的tsconfig.json。这一点很迷惑,会生成错误的编译结果。

14、ts-loaderfork-ts-checker-webpack-pluginawesome-typescript-loader

ts-loader单独使用,无命令行类型校验提示,且项目越多编译越慢。 ts-loader需要与fork-ts-checker-webpack-plugin一起使用,可以做到文件更新后秒编译,在命令行显示类型校验,但消耗内存多。 awesome-typescript-loader消耗内存稍少,文件更新时编译快,且可以显示命令行类型校验。例如更改的是依赖较多的文件例如被view依赖的更底层的store,编译速度会稍微慢。

15、typescriptpreactweb 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扩展属性时造成冲突导致无法运行。