当create-react-app遇上typescript
• • ☕️ 2 min read背景
2018 年以来的跑了许久的 react 项目,一直是基于 cra(create-react-app)官方脚手架并且不 eject,通过 react-app-rewired 进行自定义配置的。当时虽然有不少第三方的 ts(typescript)化方案,但不少官方支持且需要 eject 且需要第三方 ts loader,所以都在可升级性上有硬伤。而快到年底的时候,Babel7 出来,并且 babel 官方支持 typescript,与此同时 Facebook 将 cra2.0 加入了 ts 的支持,支持生成 ts 项目(cra-doc)。
本文尝试将我们工程的升级过程和采坑进行总结和分享。
原工程主要配置是
- create-react-app (1.x )
- flow
- react-app-rewired (1.x)
目标升级后的配置是
- create-react-app (2.x )
- typescript
- react-app-rewired (2.x)
步骤拆解
1、升级 cra 版本
首先升级react-scripts react-app-rewired
到 2.x 版本
2、重命名文件名
将 src 下的 js 和 jsx 文件改成 tsx 文件。可以利用find ./src -name "*.jsx" -exec sh -c 'mv "$0" "${0%.jsx}.tsx"' {} \;
和find ./src -name "*.js" -exec sh -c 'mv "$0" "${0%.jsx}.tsx"' {} \;
两条指令。
3、启动项目
运行npm start
,cra 会检测到项目是 typescript 工程然后为你产生一个 tsconfig.json 文件。建议将 compilerOptions 的 strict 值改成 false,以避免过分严格的 any 检查导致项目编不过。这个文件大致长这样:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false, // default is true
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": ["src"]
}
4、快捷安装 typings
可以全局安装types-installer来进行自动化的依赖解析和安装。
$ npm i -g types-installer
$ types-installer
5、修复所有编译错误
这是工作量最大也是最复杂的步骤。根据工程文件多少可能会花费 1 天到若干天的工作量。但既然选择了开始,就请坚信结果是美好的。
一个 比较复杂的带有 redux 的 react 组件的通常 ts 写法
- 这里 redux 的 state 和 dispatch 的 any 声明是可以通过引入 store 文件来实现的,偷懒了。
- RouteComponentProps 是为了 withRouter 可以正确识别 this.props.history 等 react-router 属性。
- CSSProperties 是为了让 style 对象可以正确被识别。当然工程中用的是 styled-components 就不需要这条了。
import React, {Component, CSSProperties} from 'react';
import {Route, withRouter, RouteComponentProps} from 'react-router-dom';
import {connect} from 'react-redux';
interface OwnProps {}
interface StateProps {}
interface DispatchProps {}
type Props = OwnProps & StateProps & DispatchProps & RouteComponentProps;
type State = {};
class Hello extends Component<Props, State> {
state: State = {};
static defaultProps = {};
render() {
const myStyle = {
background: 'red'
} as CSSProperties;
return <div style={myStyle}>hellow</div>;
}
}
const mapStateToProps = (state: any) => ({
state
});
const mapDispatchToProps = (dispatch: any) => ({dispatch});
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Hellow));
absolute 引用的解决方案
在通常的工程中,我们会设定 alias 来方便绝对地址的引用,比如用@记号来指向 src 路径。
通常在 config-overrides 里面这么配置
module.exports = {
webpack: function(config, env) {
config.resolve = {
...config.resolve,
alias: {
'@': path.resolve(__dirname, 'src')
}
};
}
};
这个在 cra 的 typescript 工程中编译是可以的,但 typescript 解析器默认是不支持的(会出现红色波浪线告诉你@无法识别),通过改 tsconfig 的方法并没有得到官方的支持。经过探索和试验,目前最完美的方案就是软链接src路径到node_modules/@。而且以后官方支持配置之后,也可以方便的切过去。
对 package.json 部分配置如下。
{
"scripts": {
"postinstall": "yarn link-modules",
"unlink-modules": "rimraf node_modules/@",
"link-modules": "globstar --node -- lnk \"./src/*\" \"./node_modules/@/\"",
"relink-modules": "yarn unlink-modules && yarn link-modules"
}
}
需要的依赖则可以用指令安装:
yarn add -D rimraf lnk-cli globstar
现在只要运行yarn
或者npm i
就会自动链接src到node_modules/@了。
总结
typescript 在复杂大型企业项目中越来越体现出其优势,我们将会坚定不移的转到 typescript 上来。
Refs
Typescript Tips Series: Proper Typing of react-redux Connected Components cra adding-typescript