Typescript使用小结

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 { }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容