chenxiaochun / blog

🖋️ChenXiaoChun's blog
181 stars 15 forks source link

TypeScript 开发总结 #58

Open chenxiaochun opened 6 years ago

chenxiaochun commented 6 years ago

image

最近一段时间使用 TypeScript,对遇到的问题做一些总结:

如何解决antd@Form.Create()修饰器无法使用的问题

一般我们会这样使用antd@Form.create()修饰器:

@Form.create()
class Test{}

但它可能会提示下面的类型错误:

Unable to resolve signature of class decorator when called as an expression.
Type 'ComponentClass<RcBaseFormProps & Pick<IProps, "store">, any>' is not assignable to type 'typeof Test'.
Property 'onChange' is missing in type 'Component<RcBaseFormProps & Pick<IProps, "store">, any, any>' but required in type 'Search'.

解决办法就是将修饰器改成使用函数调用的方式:

class Test{}
Form.create()(Test)

antd 官方解释:https://github.com/ant-design/ant-design/issues/6489

如何解决以下提示store类型缺失的问题

Property 'store' is missing in type '{}' but required in type 'Readonly<RcBaseFormProps & Pick<IProps, "store">>'.

假如我有一个Search.tsx,它的源码大概是这样的:

import rollbackEdit from 'store/rollbackEdit'

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)

之所以会有最上面的错误类型提示,是因为Search组件的IProps中定义了store这个类型是必须的。但是,在constructor中引用this.props中的store时,却无法引用到它。

解决此问题,有两种方法。

第一种方法:

  1. 去掉construct中对store的引用
  2. 在类方法中针对this.store的引用都改成const { rollbackEdit } = this.props.store!。最后的叹号表示忽略去掉前面引用中的nullundefined
  3. IProps的定义改成如下的样子。没错儿,只是将store:改成了store?:
    interface IProps {
    store?: {
    rollbackEdit: RollbackEditStore
    }
    form: WrappedFormUtils
    }

第二种方法:

如果使用第一种方法,觉得改动量太大,有些麻烦。那么可以尝试下面这个方法。它既然提示在组件的props上不到store这个类型定义,那我们在调用Search组件的地方,给它的props传入store不就可以了。

比如,在index.tsx中:

const store = {
  rollbackEdit,
}

const RollbackEdit = () => (
  <Search store={store} />
  <List />
)

如何解决普通类中的方法定义必须被初始化的问题

我们定义了一个List组件:

class List {
  fetchList: () => void
}
Property 'fetchList' has no initializer and is not definitely assigned in the constructor.

tslint 就会提示你fetchList必须进行初始化。这时,最简单的解决方法可以去这样定义fetchList方法:

class List {
  fetchList!: () => void
}

fetchList后面加了一个叹号。但是,如果每次都这样写,好像有点儿麻烦。那可以在 tslint 配置文件中添加一条配置:"strictPropertyInitialization": false

这样看似解决了问题,但其实并不严谨。因为一个方法如果只是被定义了,并没有进行任何实现。是不应该写在普通类里的,而应该把它抽离到一个抽象类中,然后由普通类来继承它就可以了。所以,我们的代码大概应该改成这样:

abstract class AList {
  fetchList: () => void
}

class class List extends AList{}

这样就不会有任何验证错误了,而且更符合代码的编写规范。

如何解决 TypeScript 引起的 webpack split chunk 失效问题

我们的项目代码是从 jsx 逐渐过渡到 tsx 的。在将router.jsx改为router.tsx之后,发现 webpack split chunk 就失效了。

通过 google 搜索发现了 webpack 官方的这个帖子:https://github.com/webpack/webpack/issues/5703

它的解决办法是直接在 tsconfig.json 配置中将module: "commonjs"改为"module": "esnext"。但是在我的 webpack 配置文件中又引用了很多以 commonjs 规范编写的组件。最终经过权衡,解决办法就是在 webpack 的ts-loader中配置一下这个选项,这样只会影响编译时的代码环境,而不会影响当前的源代码环境。

options: {
    compilerOptions: {
        module: "esnext",
    },
},

变量导出不能作为一个类型,即使这个变量是从一个 class 赋值而来

// A.ts
class A {}
const A1 = A
export A1

// B.ts
import { A1 } from './A'

看上面代码示例,我定义一个 class A,然后把它赋值给了 A1,并进行了导出。那么在 B 文件中我将 A 导入,可以作为一个类型定义吗?抱歉,不行。如果想使用 A 的类型定义,必须直接将 class A 本身导出才可以。

// A.ts
export class A {}

// B.ts
import { A1 } from './A'

如何定义高阶组件的children类型

interface IProps {
  children?: React.ReactNode
}

const CardTitle = ({ children }: IProps) => (
  {children || null}
)

React 的类型定义文件中对ReactNode的定义是:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

如何让 vim ale tslint 忽略对 node_modules 下的文件检查

我发现即使在tslint.jsontsconfig.json中配置了以下参数,tslint 依然会顽固的会对 node_modules 中的 ts 文件进行检查,怎么也忽略不了。

{
  "exclude": ["node_modules"]
}

解决这个问题的第一个方法是修改 ale 的配置,直接在.vimrc中添加以下配置:

let g:ale_pattern_options = {
\   'node_modules': {'ale_enabled': 0},
\}

解决问题的第二个办法:

在 vim 中使用:ALEInfo查看当前文件的信息,发现这个文件被打开之后,是处于一个临时目录中:

/var/folders/bk/m7z1498j1dg3vx83lvy0v4vr0000gn/T/nvimzuNNoK/2/Form.d.ts

因此,也就导致了在 tslint 的配置文件中添加忽略node_modules是不起使用的。所以应该在配置文件中添加这个临时目录才行。这里还要注意,在 linux 或者 mac 系统中显示的临时目录不一定是一样的:

{
  "exclude": ["node_modules", "/var/folders/**"]
}

此问题,只会发生于 vim 中。如果你用的是 vscode 则不必担心。