使用 Typescript 写 React 遇到的一些坑

自从学了 Typescript 之后,写 React 项目什么的就一直没离开过 TS 了。虽然 Typescript 大法好,不过鉴于 Typescript 严格的类型机制(事实上从某种意义上说一点都不严),导致在使用 Typescript 开发 React 的时候遇到了一些小小的问题(对 TS 好感度–)……这里就简单的记录一下。

使用 ES7 装饰符 (decorator) 时提示返回类型未定义

这个问题发生在使用 redux-zero (大概是个懒人版的 Redux)的时候。

一般来说,connect 一个组件大概要这样写,不管是用 react-redux 还是 redux-zero:

import { connect } from 'redux-zero/react';

/* ... */

class MyComponent extends React.Compoent {
	render() {
		return (/* ... */);
	}
}

export default connect(mapStateToProps, actions)(MyComponent);

有时候我们可以偷懒用上 ES7 的 decorator 特性写得简洁一些:

@connect(mapStateToProps, actions)
export default class MyCompoent extends React.Component {
	// ...
}

这两种写法效果是一样的。然而在 Typescript 中,以上代码会报这样的错误:

[ts] Unable to resolve signature of class decorator when called as an expression.

点一下 connect 方法的定义,可以看到在 connect.d.ts 的 17 行有这样的一句:

export default function connect(mapToProps: any, actions?: {}): (Child: any) => (props: any) => JSX.Element;

看着似乎没什么问题,但是:

// test 1
const test = (component: any) => {
	console.log(component);
}
@test
class MyComponent extends React.Component {
	// ...
}
// passed

把定义 test 的方法换一下:

// test 2
function test(component: any) {
	console.log(component);
}
@test
class MyComponent extends React.Component {
	// ...
}
// compile failed

看起来 Typescript 并不允许把用 function 定义的函数作为装饰符来使用呢。但是我们总不能去改依赖的代码吧,这个时候怎么办呢w

如果一些项目有 DefinitelyTyped 的 typing 文件的话,可以尝试安装 @types/xxxxx, 例如 @types/react-redux

如果没有的话,答案就是自己再包上一层,以 redux-zeroconnect() 为例:

// utils/connect.ts
import { connect as connectComponent } from 'redux-zero/react';

export const connect = (mapStateToProps: any, actions: any) => {
  return (target: any) => (
    connectComponent(mapStateToProps, actions)(target) as any
  );
};

// component
import { connect } from 'utils/connect';
/* ... */
@connect(mapStateToProps, actions)
export defalt class MyComponent extends React.Component {
	// ...
}

这样就可以啦。

自定义 JSX 元素在 JSX.IntrinsicElements 不存在

我并不知道怎么描述这个问题比较合适,场景是需要批量渲染一个对象中所有的组件:

const components = {
	comp1: Compnent1,
	comp2: Component2,
	// ...
},
	keys = Object.keys(components);

const res: Array<any> = [];
keys.forEach(key => {
	const tmp: any = components[key];
	res.push(<tmp key={key} />);
});

上面的代码会返回这样的错误:

[ts] Property 'tmp' does not exist on type 'JSX.IntrinsicElements'.

原因是 Typescript 要求 JSX 组件变量名的第一个字母为大写,如果不是的话 TS 便认为它不是个合法的 JSX 元素。所以把 tmp 改成 Tmp 就可以惹。