最近在学习typescript,所以试着用typescript来写react项目,记录一下过程中遇到的一些问题及解决方法。
一、项目搭建
这里使用create-react-app脚手架搭建基于typescript的react项目,根据文档执行以下命令:
npx create-react-app my-app --typescript
创建好了之后会是如下的结构:
现在执行
cd my-app && yarn start
可以看到项目正常运行了。
如果遇到启动报错问题,一般删除node_modules然后重新yarn install即可。
二、常见问题
项目搭建好之后我们就要开始着手项目开发,下面写一些开发中常见的问题。
1、路径别名设置
在开发中我们经常需要在当前文件内引入别的组件或者一些方法,如果都使用绝对或者相对路径来引用会变得很麻烦,所以一般都会在webpack的配置里面添加路径别名来解决这个问题:
resolve:{
//一些其他的配置
alias:{
'@': paths.appSrc //这里的paths.appSrc就是src路径,详细请自己参阅config/paths.js
}
//一些其他的配置
}
如果是js版本,这样的话已经ok了,我们也可以正常的使用如下方式引入
import { someComponent } from '@/components/someComponent';
但是在ts版本,这里会报错提示找不到模块,解决办法为:需要在tsconfig.json里加上如下配置:
"compilerOptions":{
//这里是一些已经存在的其他配置
//如下是需要添加的配置
"baseUrl": "src",
"paths": {
"@/*": [
"./*"
]
}
}
2、css模块化
脚手架搭建好的项目已经配置了css模块化,预编译器采用的sass,所以你可以直接使用:
//app.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';
import styles from './app.module.scss';
import MceLayout from './routes/layout/layout';
import { Button } from 'antd';
const App: React.FC = () => {
return (
);
}
export default App;
//app.module.scss
.app {
color: #fff
}
但是你需要遵循后缀以.module.scss || .module.sass为后缀,正如webpack里配置好的规则:
//webpack.config.js
40 // style files regexes
41 const cssRegex = /\.css$/;
42 const cssModuleRegex = /\.module\.css$/;
43 const sassRegex = /\.(scss|sass)$/;
44 const sassModuleRegex = /\.module\.(scss|sass)$/;
423 // Opt-in support for SASS (using .scss or .sass extensions).
424 // By default we support SASS Modules with the
425 // extensions .module.scss or .module.sass
426{
427 test: sassRegex,
428 exclude: sassModuleRegex,
429 use: getStyleLoaders(
430 {
431 importLoaders: 2,
432 sourceMap: isEnvProduction && shouldUseSourceMap,
433 },
434 'sass-loader'
435 ),
436 // Don't consider CSS imports dead code even if the
437 // containing package claims to have no side effects.
438 // Remove this when webpack adds a warning or an error for this.
439 // See https://github.com/webpack/webpack/issues/6571
440 sideEffects: true,
441},
3、antd组件样式问题
如果项目用了antd,为了让组件的样式和组件一样模块化加载(不做配置的话需要全局引入antd的样式文件,这样有些没用到的组件的样式也加载了,影响性能),需要在package.json里进行如下配置:
"babel": {
//一些已经存在的配置
//添加如下配置
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": "css"
}
]
]
}
4、react组件写法问题
在js版本(es6)的有状态组件里,写法大致如下:
//子组件
import React from 'react';
export default class Child extends React.Component{
constructor(props){
super(props);
this.state = {
count: 0
}
}
render(){
return(
click to add
)
}
}
//父组件
import React from 'react';
import Child from './child';
export default class Parent extends React.Component{
constructor(props){
super(props);
this.state = {
count: 0
}
}
add = ()=>{
this.setState({
count: this.state.count + 1
})
}
render(){
return(
)
}
}
这样在子组件内调用父组件传递的值是可以的。但是,在typescript版本,需要在子组件内定义接口来声明props和state的类型,否则this.props.say()会报错提示say不存在:
类型“Readonly<{}> & Readonly<{ children?: ReactNode; }>”上不存在属性“add”。ts(2339)
如下解决,修改child.tsx(typescript版的react组件后缀为.tsx):
import React from 'react';
//定义props和state的类型接口
interface IProps {
add: () => void; // () => void表示函数类型
}
interface IState {
msg: string;
}
//把props和state的类型接口传入组件
export default class Child extends React.Component{
constructor(props: IProps){
super(props);
this.state = {
msg: 'hello world'
}
}
render(){
return(
click to add
)
}
}
注意:你可能会发现就算不传入state的类型,也可以正常访问this.state.xx,但是这样typescript就无法对state进行类型检查。导致也许你访问了一个state里不存在的属性也不会报错,就失去了使用typescript的意义。
5、http代理
业务场景:我们本地开发的时候调用某个接口,会存在跨域问题,此时我们需要采用代理来规避跨域。
如果只代理一个api地址,则只需要在package.json内添加如下内容:
"proxy": "http://api02.aliyun.venuscn.com" // 这里是以阿里云的免费api地址为例,换成你自己需要的地址即可
如果需要代理多个api地址,需要安装http-proxy-middleware:
yarn add @types/http-proxy-middleware
注意:在typescript环境下,安装所有的依赖包都需要以@types/为前缀,否则无法引入。
然后在src目录下新建setupProxy.js文件,并按照如下格式填写:
//setupProxy.js
//注:下面的例子都是以阿里云的免费api,实际项目中改为自己的地址即可
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(proxy(
'/province',
{
target: 'http://api02.aliyun.venuscn.com',
changeOrigin: true,
secure: false,
pathRewrite: {
'^/province': '/',
},
},
));
app.use(proxy(
'/poetry',
{
target: 'http://jisutssbs.market.alicloudapi.com',
changeOrigin: true,
secure: false,
pathRewrite: {
'^/poetry': '/',
},
},
));
};
你的http请求文件中:
axios.get('/province/area/all?level=0');//此请求最终为:http://api02.aliyun.venuscn.com/area/all?level=0
axios.get('/poetry/tangshi/search?keyword=无');//此请求最终为:http://jisutssbs.market.alicloudapi.com/tangshi/search?keyword=无
注意:虽然本文是基于typescript,但是这个配置文件目前看来只能以.js为后缀(待验证),如果你执行了npm run eject后,会发现在config目录下的paths.js里配置的是setupProxy.js:
//...
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'), //这里是引用的setupProxy.js文件
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
};
更多内容请参阅官方传送门
6、代码split,按需加载
这里我们使用react-loadable来进行代码分割,用法如下:
//router.ts
import React from 'react';
import { Route } from 'react-router-dom';
import Loadable from 'react-loadable';
import loading from './loading';
const Home = Loadable({
loader: () => import('@/routes/home/view'),
loading,
});
const routes = [
}
/>,
];
export default routes;
这里在安装的时候遇到一个问题,首先执行:
yarn add @types/react-loadable
会发现我们的引入的地方没有提示找不到该模块了,但是运行却报错:
我们查看一下node_modules,发现实际上node_modules里面并没有安装该模块,仅仅是在@types里面添加了该模块的声明,所以还需要安装一下该模块:
yarn add react-loadable
重新启动即可。
源码github地址
该demo持续更新,所以github上面的代码和文章内的可能会有一些出入,最终以github上的为准,未完待续...