TypeScript 开发经验分享

typescript

语法

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

添加了declare namespace JSXdeclare module 'react'javascript

不起做用,并且引入新的ts校验错误css

按照html5标准建议,只要是自定义标签,都应使用data-开头的定义html

  • 最终解决办法,将
<a clstag={`....`}>{children}</a>
复制代码

改成以下格式,勉强骗过校验,达到不报错的地步。 正路仍是应该寻找扩展HTMLAnchorElement属性的方法前端

const props = {
  clstag: '...'
}
<a {...props}>{children}</a>
复制代码

二、 关于mobx-react@inject已注入属性,但在使用该组件时仍是类型校验提示缺乏属性的问题

  • 参考 经过一个自定义get injected属性来避免直接获取props的方法,须要在每一个组件中都多写一个方法html5

  • 采用方案,在注入属性后使用!代表该属性确认存在。java

    const { router } = this.props.store!
    复制代码

三、 在运行时经过扩展添加属性的对象,用&

例如在store/header中使用了storePropnode

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中的全部属性react

四、要求每一个实例的属性都有初始值,初始值既能够在 constructor 中设置,也能够在声明时设置

Property 'fetchBatch' has no initializer and is not definitely assigned in the constructor.
复制代码

若是一个普通类中的方法只是被定义了,而没有进行任何实现,便会报以上错误,解决办法之一是这样写:webpack

public restoreBatch!: () => void
复制代码

若是每次都这样写确实有些麻烦,因此能够在tsconfig.json添加如下配置:git

"strictPropertyInitialization": false,

复制代码

可是,以更严谨的角度来讲,未进行实现的任何方法都不该该写在普通类里,而应该放在抽象类里。所以,遇到以上状况时,代码应该如下方式来实现:

abstract class A{
  public restoreBatch: () => void
}

class B extends A{}
const b = new B()
export default b
复制代码

五、变量导出不能做为类型,即便这个变量是从一个class赋值而来

class A {}
export const A1 = A

// other file
import { A1 } from 'A'
// 此时A1仅仅做为一个变量使用,没法做为类型来使用的。
// 是个坑 🥵 !
复制代码

六、高阶组件的children类型定义

例如src/component/Permission组件的children类型应该定义为children: React.ReactNode。在 React 中将ReactNode的类型定义成了:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
复制代码

七、antd 的Form.create()修饰器

此修饰器目前在 ts 中没法使用,只能换成 function 的形式

例如:

@Form.create()
class Test{}
复制代码

换成:

class Test{}

Form.create()(Test)
复制代码

官方解释

八、解决如下异常的两种方法

异常信息:

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> ) 复制代码

九、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',
  },
},
复制代码

十、函数类型的双变性

具体可查看:函数类型的双变性

十一、类型守卫与类型区分

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();
}
复制代码

十二、css-module的类型检查

一开始绕了弯路,用typescript-plugin-css-modules,对less支持很差,当使用复杂less函数时会失效,在更新less文件时还常常须要重启tsserver。 后来改进参考以下: typings-for-css-modules-loader

使用一个webpack loader,即时生成对应的.d.ts文件来映射css modulesclass

1三、tsc命令行参数

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

1四、ts-loaderfork-ts-checker-webpack-pluginawesome-typescript-loader

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

1五、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
        }
      }
    }
  }
}

复制代码

配置

1六、tsconfig.json的路径别名path

若是只是当前项目使用,能够尽情用

在和其余项目一块儿使用时,个人状况是做为多包的lerna项目,一个包须要引入另外一个包的ts文件,则原包中的路径别名在另外一个包中是不能工做的。

1七、.d.ts与发布

项目发布时,即便打开了tsconfig.json中的declaration,其中的.d.ts文件也不会自动编译到outDir指定的目录中去

若是项目中有定义须要依赖这些.d.ts,须要在发布过程当中添加额外的复制脚本

.d.ts中尽可能不要使用import引入其余依赖,尤为是不能引入非纯定义文件的类型,好比该文件中含有实际的逻辑代码,不然发布后在使用该包的环境中不能正常工做

1八、使用babel编译

因为项目中有不少使用abstract定义的抽象类,在ts-loader编译时抽象类只做为类型检查使用。但使用babel编译时抽象类会生成真实的类,而且其中的属性也都会与类一块儿生成。在使用mobx扩展属性时形成冲突致使没法运行。