1、使用库描述定义入参
import Input, { InputProps } from '@material-ui/core/Input';
import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles';
const styles = createStyles({
....
});
interface IProps extends WithStyles<typeof styles> {
....
}
const ZnInput: React.SFC<IProps & InputProps> = ({
classes, // 来自WithStyles的classes
inputProps // 描述Input的props
}) => (
<Input
id="input-label"
classes={{ root: classes.inputText, underline: classes.underline }}
{...inputProps}
/>
)
2、巧用查找类型,使用库描述定义接口
import { RouteComponentProps } from 'react-router'
interface IProps extends WithStyles<typeof styles> {
history: RouteComponentProps['history']
match: RouteComponentProps<{ classId: string }>['match']
}
3、使用enum定义文案、状态、通过值判断键
enum LiveStatusEnum {
'未开始',
'正在直播',
'已结束',
'回放中',
'无效直播',
}
LiveStatusEnum[status] // status值为0, 1, 2, ...输出未开始,正在直播,...
enum CourseStatusEnum {
false = '未完成',
true = '已学习',
}
CourseStatusEnum[status] // status值为ture或false,输出已学习或示学习
enum HStatusEnum {
UNSUBMITED = 0,
SUBMITTED = 1,
MARKED = 2,
BOUNCED = 3,
RECALLED = 4,
DELETED = 5,
}
HStatusEnum.UNSUBMITED === status // status值为0, 1, 2, ... 判断是否未提交,已提交...
enum SourceTypeEnum {
COURSE = 1,
EXAM = 2,
LIVE = 3,
TRAINING = 4,
QUESTIONNAIRE = 5,
INTELLIGENT = 6,
NEW_QUESTIONNAIRE = 7,
}
switch (SourceTypeEnum[resourceType]) { // 通过值求得Key
case 'COURSE':
RouterUtil.toCourse(resourceId)
break
case 'EXAM':
RouterUtil.toExam(resourceId)
break
case 'LIVE':
RouterUtil.toLive(resourceId, type)
break
}
4、空对象 、数组使用类型断言as断言类型
// 空对象 {} MyTrainingDetail/index.tsx
interface IDetailProps {
classTitle: string
...
}
const detail = {}
// const detail = {} as IDetailProps
detail.classTitle // Property 'classTitle' does not exist on type '{}'.ts(2339)
// 空数组[] StudyMap/RankList/index.tsx
interface IRankItem {
empName: string
...
}
const list = [] // const list: any[]
const empName = list[0].empName // Variable 'list' implicitly has an 'any[]' type.ts(7005)
// const list = [] as IRankItem[]
// const list = new Array<IRankItem>()
5、函数重载,重载的函数定义无法导出使用
function getElement(id: string, type: 'input'): HTMLInputElement | null
function getElement(id: string, type: 'textarea'): HTMLTextAreaElement | null
function getElement(id: string, type: string): any {
if (type === 'input' || type === 'textarea') {
return document.getElementById(id)
}
}
6、使用type与interface描述对象
接口 vs. 类型别名
-
接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。 在下面的示例代码里,在编译器中将鼠标悬停在 interfaced上,显示它返回的是 Interface,但悬停在 aliased上时,显示的却是对象字面量类型。
-
类型别名不能被extends和implements(自己也不能extends和implements其它类型)。
-
因为软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。
-
如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
7、巧用typeof类型推导
// 例1
interface IProps extends WithStyles<typeof styles> {
...
}
// 例2
const initState = { ... }
type IState = typeof initState
8、巧用显式泛型
function $<T extends HTMLElement>(id: string): T {
return document.getElementById(id)
}
// 显示指定类型
$<HTMLInputElement>('input').value
9、常用Event事件对象类型
ClipboardEvent<T = Element> 剪贴板事件对象
DragEvent<T = Element> 拖拽事件对象
ChangeEvent<T = Element> Change 事件对象
const onChange = (event: React.ChangeEvent) => {
console.log('值', event.target.innerHTML)
}
const xxx = () => (
<input onChange={onChange} name="请输入" placeholder="请输入" />
)
KeyboardEvent<T = Element> 键盘事件对象
MouseEvent<T = Element> 鼠标事件对象
const onClick = (event:React.MouseEvent) => {
console.log('偏移', event.screenX)
}
const xxx = () => (
<div onClick={onClick}>点击我</div>
)
TouchEvent<T = Element> 触摸事件对象
WheelEvent<T = Element> 滚轮事件对象
AnimationEvent<T = Element> 动画事件对象
TransitionEvent<T = Element> 过渡事件对象
10、高阶组件
interface ILoadingProps {
setLoading: (visible: boolean) => void
getData: (d: Promise<any>) => Promise<any>
getLoading: () => boolean
}
const enhanceLoading = <P extends ILoadingProps>(
Comp: React.ComponentType<P>
) => (props: Omit<P, keyof ILoadingProps>) => {
const getData = async (mayBePromise: Promise<any>) => {
setLoading(true)
const result = await mayBePromise
setLoading(false)
return result
}
const classes = styles()
const [loading, setLoading] = useState(false)
const getLoading = () => loading
return (
<React.Fragment>
{loading ? (
<div className={classes.root}>
<CircularProgress className={classes.progress} />
</div>
) : null}
<Comp {...props as P} setLoading={setLoading} getData={getData} getLoading={getLoading}/>
</React.Fragment>
)
}
11、Typescript给已有的库增加属性声明
假设现在有这样的业务场景,我需要临时给div增加一个name属性,但是在react的index.d.ts给出的描述中并没有name这个属性,所以编译器会一直提示报错:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
// 找到React.HTMLAttributes的描述,并没有name这个属性
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
...
}
<div name="yyyy">xxxx</div>
// Property 'name' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.ts(2322)
如何解决这样的问题呢?
1、新建xxx.d.ts
2、针对库(这里是react)补充声明
declare module 'react' {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
name?: string;
}
}
11.1 声明合并
介绍
声明合并是指编译器执行将两个名称相同的声明合并到一个单独的声明里的工作。合并后的声明具有两种原始声明的特性。当然,声明合并不限于合并两个声明,需要合并的声明数量可任意(注意:他们之间具有相同名称)。在TypeScript中,一个声明可以有三种情况:命名空间/模块(命名空间:内部模块;模块:外部模块)、类型、值。当声明是创建一个命名空间/模块的时候,该命名空间可通过点符号(.)来访问。创建类型的声明将会创建一个指定名称和结构的类型。最后,声明值就是那些在输出的JavaScript中可见的部分(如,函数和变量)。
(1)模块合并
看看这个例子中的Animal模块的声明合并:
module Animals {
export class Zebra { }
}
module Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
相当于:
module Animals {
export interface Legged { numberOfLegs: number; }
export class Zebra { }
export class Dog { }
}
网友评论