tsconfig.json 常用配置项注释
export class Triangle { /* ... */ }
export class Square { /* ... */ }
tsconfig.json 配置项问题
1. 三种 JSX 模式
- 在 TS 中想要使用 JSX 必须做两件事:
- 给文件一个 .tsx 扩展名
- 启用 jsx 选项
- TS 具有三种 JSX 模式:preserve,react 和 react-native,这些模式只在代码生成阶段起作用,类型检查并不受影响。
- preserve 模式下: 不会将 JSX 编译成 JS,生成代码中会保留 JSX,以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有 .jsx 扩展名。
- react 模式下: 直接将 JSX 编译成 JS,会生成 React.createElement 的形式,在使用前不需要再进行转换操作了,输出文件的扩展名为 .js。
- react-native 模式下: 相当于 preserve,它也保留了所有的 JSX,但是输出文件的扩展名是 .js。
模式输入输出输出文件扩展名preserve<div /><div />.jsxreact<div />React.createElement("div").jsreact-native<div /><div />.js
2. "lib" 配置项需要注意的问题
- 当你安装 TypeScript 时,会顺带安装 lib.d.ts 等声明文件,此文件包含了 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。
- 它自动包含在 TypeScript 项目的编译上下文中
- 它能让你快速开始书写经过类型检查的 JavaScript 代码
- tsconfig.json 中的 lib 选项用来指定当前项目需要注入哪些声明库文件。如果没有指定,默认注入的库文件列表为:
- 当 --target ES5:DOM,ES5,ScriptHost
- 当 --target ES6:DOM,ES6,DOM.Iterable,ScriptHost
- 如果在 TS 中想要使用一些 ES6 以上版本或者特殊的语法,就需要引入相关的类库。如:ES7 、DOM.Iterable
3. "moduleResolution" 解析策略
https://www.tslang.cn/docs/handbook/module-resolution.html
4. 指定 target 为 es6 时,tsc 就会默认使用 "classic" 模块解析策略,这个策略对于 `import * as abc from "@babel/types"` 这种非相对路径的导入,不能正确解析。
- 解决方法:指定解析策略为 node => "moduleResolution": "node"。
5. "esModuleInterop" 具体作用是什么
- 如果一个模块遵循 ES6 模块规范,当默认导出内容时(export default xxx),ES6 模块系统会自动给当前模块的顶层对象加上一个 default 属性,指向导出的内容。当一个 ES6 模块引入该模块时(import moduleName from 'xxx'),ES6 模块系统默认会自动去该模块中的顶层对象上查找 default 属性并将值赋值给 moduleName。而如果一个非 ES6 规范的模块引入 ES6 模块直接使用时(var moduleName = require('xxx')),就会报错,需要通过 moduleName.default 来使用。
- TypeScript 为了兼容,引入了 esModuleInterop 选项,设置 esModuleInterop 为 true ,在编译时自动给该模块添加 default 属性,就可以通过 import moduleName from 'xxx' 的形式导入 非 ES6 模块,不再需要使用 import moduleName = require('xxx') 的形式。
6. "allowSyntheticDefaultImports" 具体作用是什么
- 允许 默认导入 没有设置默认导出(export default xxx)的模块,可以以 import xxx from 'xxx' 的形式来引入模块
const enum Colors {
Red,
Yellow,
Blue
}
// 常量枚举会在编译阶段被删除
let myColors = [Colors.Red, Colors.Yellow, Colors.Blue];
7. "paths" 配置路径映射集合时,需要注意的问题
{
"paths": {
// 这里的路径后面必须跟着 "/*"
"@public/*": [
// 这里的路径后面必须跟着 "/*"
"public/*"
],
"@src/*": [
"src/*"
],
"@assets/*":[
"src/assets/*"
],
"@components/*": [
"src/components/*"
]
}
}
8. "allowJs" 时需要注意的问题
- 设置 "allowJs": false :在 .ts / .tsx 文件中引入 .js / .jsx 文件时,就不会有相关提示
image.png
React + TS 项目问题
1. 使用 import 引入非 JS 模块会报错,而使用 require 则没有问题
interface Test {
arr: string[]
}
// pick 摘取返回的结果 => {arr: string[]}
let aaa: Pick<Test, 'arr'> = {arr: ['1']};
image.png
解决办法: 给这些非 JS 模块添加声明
/**
* style
*/
declare module '*.css'
declare module '*.less'
// declare module "*.less" {
// const styles: { [className: string]: string };
// export default styles
// }
declare module '*.scss'
/**
* 图片
*/
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
2. import * as React from 'react' 和 import React from 'react' 有什么区别
- 第一种写法是将所有用 export 导出的成员赋值给 React ,导入后用 React.xxx 访问
- 第二种写法仅是将默认导出(export default)的内容赋值给 React
3. 解决 import * as xxx from 'xxx' 这种奇怪的引入方式
- 配置 tsconfig.json
// 数字索引——约束数组
// index 是随便取的名字,可以任意取名
// 只要 index 的类型是 number,那么值的类型必须是 string
interface StringArray {
// key 的类型为 number ,一般都代表是数组
// 限制 value 的类型为 string
[index:number]:string
}
let arr:StringArray = ['aaa','bbb'];
console.log(arr);
// 字符串索引——约束对象
// 只要 index 的类型是 string,那么值的类型必须是 string
interface StringObject {
// key 的类型为 string ,一般都代表是对象
// 限制 value 的类型为 string
[index:string]:string
}
let obj:StringObject = {name:'ccc'};
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
4. 对 antd 组件库进行按需加载
- 这里使用的是 ts-loader 转译 TS 方案,更多方案请看 Webpack 转译 Typescript 现有方案
.babelrc
// 注意区别
// 普通的接口
interface discount1{
getNum : (price:number) => number
}
// 函数类型接口
interface discount2{
// 注意:
// “:” 前面的是函数的签名,用来约束函数的参数
// ":" 后面的用来约束函数的返回值
(price:number):number
}
let cost:discount2 = function(price:number):number{
return price * .8;
}
// 也可以使用类型别名
type Add = (x: number, y: number) => number
let add: Add = (a: number, b: number) => a + b
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"jsx": "preserve",// 保留 jsx
...
}
webpack.config.js
{
test: /\.tsx?$/,
use: [
'babel-loader',
'ts-loader'
]
},
5. 声明通过 React.createRef()创建的 ref 类型
class Father {
str: string; // 默认就是 public
public name: string; // 在定义的类中、类的实例、子类、子类实例都可以访问
protected age: number; // 只能在定义的类和子类中访问,不允许通过实例(定义的类的实例和子类实例)访问
private money: number; // 只能在定义的类中访问,类的实例、子类、子类实例都不可以访问
constructor(name: string, age: number, money: number) {
this.name = name;
this.age = age;
this.money = money;
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
const fa = new Father('aaa', 18, 1000);
console.log(fa.name);// aaa
console.log(fa.age);// error
console.log(fa.money);// error
class Child extends Father {
constructor(name: string, age: number, money: number) {
super(name, age, money);
}
desc() {
console.log(`${this.name} ${this.age} ${this.money}`);
}
}
let child = new Child('bbb', 18, 1000);
console.log(child.name);// bbb
console.log(child.age);// error
console.log(child.money);// error
6. react + redux + react-redux 项目:使用 @connect 装饰器正常,但是一旦结合 TS 后,就会报错
https://segmentfault.com/a/1190000016047027
import {ComponentClass} from 'react'
import {
connect as nativeConnect,
MapDispatchToPropsParam,
MapStateToPropsParam
} from 'react-redux'
import {withRouter as nativeWithRouter} from 'react-router-dom'
export type ComponentDecorator<P = any> = <T extends ComponentClass<P>>(WrappedComponent: T) => T
export const connect: <P, S>(
mapState: MapStateToPropsParam<Partial<P>, P, S>,
// mapDispatch?: MapDispatchToPropsParam<Partial<P>, P>
mapDispatch?: any
) => ComponentDecorator = nativeConnect as any;
export const withRouter: ComponentDecorator = nativeWithRouter as any;
7. react + redux + react-redux 项目:在使用 mapStateToProps(state) 函数时,想要给仓库中的 state 声明类型
- 借助 ReturnType
function attr(val: string): string;
function attr(val: number): number;
// 前面两行是函数申明,这一行是实现函数重载
function attr(val: any): any {
if (typeof val === 'string') {
return val;
} else if (typeof val === 'number') {
return val;
}
}
attr('aaa');
attr(666);
// setScene 模块
import * as types from '../types/action-types';
import {appEditAction} from '../actions/common';
export interface SetSceneState {
loadSuccess: boolean;
loadProgress: number;
}
let initState: SetSceneState = {
loadSuccess: false,
loadProgress: 0,
};
export default function (state: SetSceneState = initState, action: appEditAction) {
switch (action.type) {
case types.SCENE_DATA_LOADSUCCESS: {
return {...state, loadSuccess: action.payload.success};
}
case types.SCENE_DATA_LOADINGPROGRESS: {
return {...state, loadProgress: action.payload.num};
}
default:
return state;
}
}
使用
image.png
8. react + redux + react-redux 项目:想要给 action creator 函数声明类型
// 在 Mesh 组件中
import workActions from "@store/actions/work";
interface MeshProps {
// 刚开始我是这样写的,每次都得在组件的 Props 里重新声明一下函数
// updateSceneData?: (workId: string,data) => appEditAction;
updateData?: typeof workActions.updateData;
}
@connect(null, {
updateData: workActions.updateData,
})
class Mesh extends React.Component<MeshProps> {...}
// store/actions/work.ts
import * as types from '../types/action-types';
import {appEditAction} from "@edit-store/actions/common";
export default {
updateWorkData(workId: string, data: any): appEditAction {
return {type: types.UPDATE_WORK_ASYNC, payload: {workId, data}}
}
}
9. react + redux + react-redux 项目:给 React 组件的 Props 声明类型(较为便捷的方法)
import * as React from 'react';
import {RouteComponentProps} from 'react-router';
import {connect} from "@store/connect";
import {AppState} from "@store/reducers";
import commonActions from "@store/actions/commonActions";
// 组件可能有四个属性来源
// 1.mapStateToProps 的返回值
// 2.actions 对象类型
// 3.来自路由
// 4.父组件传进来的其它属性
// 原先的写法:一个个拼起来,mapStateToProps 返回的状态还得在 Props 接口里再声明一遍,比较混乱、麻烦
// interface Props {
// loadProgress?: number;
// markVisible?: boolean;
// setMarkVisible?: typeof commonActions.setMarkVisible;
// }
function mapStateToProps(state: AppState) {
const {markVisible,loadProgress} = state;
return {
markVisible,
loadProgress,
};
}
// 现在的写法:便捷
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof commonActions;
interface IParams {}
type RouteProps = RouteComponentProps<IParams>;
type Props = StateProps & RouteProps & DispatchProps & {};
@connect(mapStateToProps, {
setMarkVisible: commonActions.setMarkVisible
})
export default class App extends React.PureComponent<Props, any> {
render() {
const {markVisible, loadProgress} = this.props;
return (<div > {markVisible} {loadProgress} </div>);
}
}
10. react + redux + react-redux 项目:想要给 redux-thunk 声明类型
redux thunk 有一个内置类型 ThunkAction,我们可以这样使用:
// src/thunks.ts
import { Action } from 'redux'
import { sendMessage } from './store/chat/actions'
import { AppState } from './store'
import { ThunkAction } from 'redux-thunk'
export const thunkSendMessage = (
message: string
): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {
const asyncResp = await exampleAPI()
dispatch(
sendMessage({
message,
user: asyncResp,
timestamp: new Date().getTime()
})
)
}
function exampleAPI() {
return Promise.resolve('Async')
}
11. 使用 webpack 的 module.hot 会警告没有类型定义
# 下载这个类型声明文件
nbsp;npm install --save @types/webpack-env
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer));
}
}
12. tsconfig-paths-webpack-plugin 这个包会将 tsconfig.json 中的 path 配置项内容映射到 webpack 配置中去,这样就不需要在 webpack 中的 alias 配置项里配置路径映射
image.png
13. react 函数组件声明
interface Greeting {
name: string;
age: number;
}
const Hello:React.FC<Greeting> = (props) => <h1>Hello {props.name}</h1>;
// 推荐使用第二种
const Hello2 = (props:Greeting) => <h1>Hello {props.name}</h1>;
14. 如何编写 react + ts 版的 HOC
import React, { Component } from 'react';
import HelloClass from './HelloClass';
interface Loading {
loading: boolean
}
// HOC 可以接收一个类组件,也可以接收一个函数组件,所以参数的类型是 React.ComponentType
// 源码:type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
function HelloHOC<P>(WrappedComponent: React.ComponentType<P>) {
return class extends Component<P & Loading> {
render() {
const { loading, ...props } = this.props;
return loading ? <div>Loading...</div> : <WrappedComponent { ...props as P } />;
}
}
}
export default HelloHOC(HelloClass);
15. 快速获取事件处理函数的 event 参数类型
class Login extends React.Component <Props>{
handlerLinkBtnClick = (ev) => {
console.log(ev);
this.props.historyGo('./register');
};
handlerLinkBtnMouseMove = (ev) => {
console.log(ev);
};
render() {
return (
<div>
<header>
<p >This is Login Page </p>
<div className={styles.linkBtn}
onMouseMove={this.handlerLinkBtnMouseMove}
onClick={this.handlerLinkBtnClick}>
Go to Register Page
</div>
</header>
</div>
);
}
}
按住 Ctrl ,然后鼠标移动到事件名上就能获取当前事件处理函数的参数类型