likes
comments
collection
share

typescript中常规的dom元素和event事件类型声明

作者站长头像
站长
· 阅读数 59

一. 前言

我们在日常的开发中,不管是Vue还是React在获取一个dom元素,或者是通过一个input框一类的获取到value的时候,会出现一些类型声明的问题。比如:

在Vue3.0中:

    <template>
        <button @click="btnClick">获取e</button>
    </template>

typescript中常规的dom元素和event事件类型声明 这里如果我们给e声明一个Event类型,很明显会出现一个类型错误。

但是我们console.log(e.x)是可以获取到值的。所以这个e的类型是不准确的。

所以合理的声明一个类型,在一些dom操作中是很有必要的。那我们现在就开始了解一下ts中的dom元素类型和event事件类型有哪些,以及如果合理的进行类型声明。

二. 类型解析

1. HTMLElement 和 Element

在template中写一个div:

    <div id="divClick"></div>
     const docu = document.getElementById('divClick');
     const docu1 = document.querySelector('#divClick');

把鼠标分别放在docu和docu1上:

typescript中常规的dom元素和event事件类型声明

typescript中常规的dom元素和event事件类型声明

可以看出,两者获取的节点类型不同。一个是HTMLElement、 一个是Element。两者有什么联系和区别呢?

HTMLElement

HTMLElementHTML 文档中某个元素的具体类型,该类型包含了所有 HTML 元素的共有属性和方法,如 CSSStyleDeclarationEventTarget

Element

ElementHTMLElement 的基本类型,它表示文档中任何类型的标记元素(如 divspanp 等),包括非 HTML 元素。 Element 类型只包含最基本的属性(如 tagNameattributes)和方法(如 getAttribute()setAttribute())。

HTMLElement 和 Element 两者区别

Element 是一个通用的基类,所有 Document 对象下的对象都继承自它。这个接口描述了所有相同种类的元素所普遍具有的方法和属性。一些接口继承自 Element 并且增加了一些额外功能的接口描述了具体的行为。例如, HTMLElement 接口是所有 HTML 元素的基本接口,而 SVGElement 接口是所有 SVG 元素的基础。大多数功能是在这个类的更深层级的接口中被进一步制定的。

  • HTMLElement是extends Element、然后额外增加一些额外的接口。
  • Element是所有元素的基类。

在node_modules下面的typescript/lib/lib.dom.d.ts:

    interface HTMLElement extends Element, ElementCSSInlineStyle, ElementContentEditable, GlobalEventHandlers, HTMLOrSVGElement {
        accessKey: string;
        readonly accessKeyLabel: string;
        autocapitalize: string;
        dir: string;
        draggable: boolean;
        hidden: boolean;
        inert: boolean;
        innerText: string;
        lang: string;
        readonly offsetHeight: number;
        readonly offsetLeft: number;
        readonly offsetParent: Element | null;
        readonly offsetTop: number;
        readonly offsetWidth: number;
        outerText: string;
        spellcheck: boolean;
        title: string;
        translate: boolean;
        attachInternals(): ElementInternals;
        click(): void;
        addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
        addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
        removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
        removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

可以看出,HTMLElement是继承Element的

具体的dom元素类型声明

虽然说每次获取dom结点,都可以通过设置类型为HTMLElement。但是有的时候也会有问题。

    <input type="text" value="hello">
    
    const input = document.querySelector('input[type=text]') as HTMLElement;
    console.log(input!.value);   // 类型“HTMLElement”上不存在属性“value”

这时候,我们就需要把它设置为更具体一点的dom类型。

如上的例子,我们可以设置为HTMLInputElement

      const input = document.querySelector('input[type=text]') as HTMLInputElement;
       console.log(input!.value);

为什么设置为HTMLInputElement,TS就不报错呢?

这个要从typescript/lib/lib.dom.d.ts:中找答案

    interface HTMLInputElement extends HTMLElement {
         ...
        /** Gets or sets a string containing a regular expression that the user's input must match. */
        pattern: string;
        /** Gets or sets a text string that is displayed in an input field as a hint or prompt to users as the format or type of information they need to enter.The text appears in an input field until the user puts focus on the field. */
        placeholder: string;
        readOnly: boolean;
        /** When present, marks an element that can't be submitted without a value. */
        required: boolean;
        selectionDirection: "forward" | "backward" | "none" | null;
        /** Gets or sets the end position or offset of a text selection. */
        selectionEnd: number | null;
        /** Gets or sets the starting position or offset of a text selection. */
        selectionStart: number | null;
        size: number;
        /** The address or URL of the a media resource that is to be considered. */
        src: string;
        /** Defines an increment or jump between values that you want to allow the user to enter. When used with the max and min attributes, lets you control the range and increment (for example, allow only even numbers) that the user can enter into an input field. */
        step: string;
        /** Returns the content type of the object. */
        type: string;
         /** Returns the value of the data at the cursor's current position. */
        value: string;
        ...
}

原来如此: HTMLInputElement是继承HTMLElement的,基于HTMLElement进行扩展的,所以当然没有问题。

那下次定义的类型的时候就不要再一直HTMLInputElement的写到底啦,根据你想要的类型进行基本细分啦!

这里总结一些继承HTMLElement的类型。

    interface HTMLElementTagNameMap {
        "a": HTMLAnchorElement;
        "abbr": HTMLElement;
        "address": HTMLElement;
        "applet": HTMLAppletElement;
        "area": HTMLAreaElement;
        "article": HTMLElement;
        "aside": HTMLElement;
        "audio": HTMLAudioElement;
        "b": HTMLElement;
        "base": HTMLBaseElement;
        "bdi": HTMLElement;
        "bdo": HTMLElement;
        "blockquote": HTMLQuoteElement;
        "body": HTMLBodyElement;   // *
        "br": HTMLBRElement;
        "button": HTMLButtonElement;  // *
        "canvas": HTMLCanvasElement;  // *
        "caption": HTMLTableCaptionElement;
        "cite": HTMLElement;
        "code": HTMLElement;
        "col": HTMLTableColElement;
        "colgroup": HTMLTableColElement;
        "data": HTMLDataElement;
        "datalist": HTMLDataListElement;
        "dd": HTMLElement;
        "del": HTMLModElement;
        "details": HTMLDetailsElement;
        "dfn": HTMLElement;
        "dialog": HTMLDialogElement;
        "dir": HTMLDirectoryElement;
        "div": HTMLDivElement;
        "dl": HTMLDListElement;
        "dt": HTMLElement;
        "em": HTMLElement;
        "embed": HTMLEmbedElement;
        "fieldset": HTMLFieldSetElement;
        "figcaption": HTMLElement;
        "figure": HTMLElement;
        "font": HTMLFontElement;
        "footer": HTMLElement;
        "form": HTMLFormElement;
        "frame": HTMLFrameElement;
        "frameset": HTMLFrameSetElement;
        "h1": HTMLHeadingElement;
        "h2": HTMLHeadingElement;
        "h3": HTMLHeadingElement;
        "h4": HTMLHeadingElement;
        "h5": HTMLHeadingElement;
        "h6": HTMLHeadingElement;
        "head": HTMLHeadElement;
        "header": HTMLElement;
        "hgroup": HTMLElement;
        "hr": HTMLHRElement;
        "html": HTMLHtmlElement;
        "i": HTMLElement;
        "iframe": HTMLIFrameElement;
        "img": HTMLImageElement;  // *
        "input": HTMLInputElement;  // * 
        "ins": HTMLModElement;
        "kbd": HTMLElement;
        "label": HTMLLabelElement;
        "legend": HTMLLegendElement;
        "li": HTMLLIElement;
        "link": HTMLLinkElement;
        "main": HTMLElement;
        "map": HTMLMapElement;
        "mark": HTMLElement;
        "marquee": HTMLMarqueeElement;
        "menu": HTMLMenuElement;
        "meta": HTMLMetaElement;
        "meter": HTMLMeterElement;
        "nav": HTMLElement;
        "noscript": HTMLElement;
        "object": HTMLObjectElement;
        "ol": HTMLOListElement;
        "optgroup": HTMLOptGroupElement;
        "option": HTMLOptionElement;
        "output": HTMLOutputElement;
        "p": HTMLParagraphElement;
        "param": HTMLParamElement;
        "picture": HTMLPictureElement;
        "pre": HTMLPreElement;
        "progress": HTMLProgressElement;
        "q": HTMLQuoteElement;
        "rp": HTMLElement;
        "rt": HTMLElement;
        "ruby": HTMLElement;
        "s": HTMLElement;
        "samp": HTMLElement;
        "script": HTMLScriptElement;
        "section": HTMLElement;
        "select": HTMLSelectElement;
        "slot": HTMLSlotElement;
        "small": HTMLElement;
        "source": HTMLSourceElement;
        "span": HTMLSpanElement;
        "strong": HTMLElement;
        "style": HTMLStyleElement;
        "sub": HTMLElement;
        "summary": HTMLElement;
        "sup": HTMLElement;
        "table": HTMLTableElement;
        "tbody": HTMLTableSectionElement;
        "td": HTMLTableDataCellElement;
        "template": HTMLTemplateElement;
        "textarea": HTMLTextAreaElement;
        "tfoot": HTMLTableSectionElement;
        "th": HTMLTableHeaderCellElement;
        "thead": HTMLTableSectionElement;
        "time": HTMLTimeElement;
        "title": HTMLTitleElement;
        "tr": HTMLTableRowElement;
        "track": HTMLTrackElement;
        "u": HTMLElement;
        "ul": HTMLUListElement;
        "var": HTMLElement;
        "video": HTMLVideoElement;  // *
        "wbr": HTMLElement;
}

     * 是个人平时用到的较多的类型。

2. Event

总结了一下dom类型声明,下面我们来看一下事件类型。

在文章开头的地方我们写了一个例子,那里使用Event类型会报错,那到底什么是Event类型呢?

Event类型介绍

Event接口表示一个DOM事件,包含了事件的基本信息,如事件类型、目标元素等。可以使用该接口来定义事件的类型

可以理解为Event类型是事件的基本类型,所有不同类型的event事件都是继承于Event类型。

简单写一个继承。例如:

    <template>
        <div @click="clickMyEvent">MyEvent</div>
    </template>
    
    interface MyEvent extends Event {
        mess: string
    }
    
    
    const clickMyEvent = (e: MouseEvent) => {
        // 这里的MyEvent就是继承自Event的类型。
        let targetMouseEvent: MyEvent = {
            mess: 'ok',
            ...e
        }  
    }

常见的事件类型

总体事件类型

先看一下总的事件类型。回到 typescript/lib/lib.dom.d.ts

    interface GlobalEventHandlersEventMap {
        "abort": UIEvent;
        "animationcancel": AnimationEvent;
        "animationend": AnimationEvent;
        "animationiteration": AnimationEvent;
        "animationstart": AnimationEvent;
        "auxclick": MouseEvent;
        "beforeinput": InputEvent;
        "blur": FocusEvent;
        "cancel": Event;
        "canplay": Event;
        "canplaythrough": Event;
        "change": Event;
        "click": MouseEvent;
        "close": Event;
        "compositionend": CompositionEvent;
        "compositionstart": CompositionEvent;
        "compositionupdate": CompositionEvent;
        "contextmenu": MouseEvent;
        "copy": ClipboardEvent;
        "cuechange": Event;
        "cut": ClipboardEvent;
        "dblclick": MouseEvent;
        "drag": DragEvent;
        "dragend": DragEvent;
        "dragenter": DragEvent;
        "dragleave": DragEvent;
        "dragover": DragEvent;
        "dragstart": DragEvent;
        "drop": DragEvent;
        "durationchange": Event;
        "emptied": Event;
        "ended": Event;
        "error": ErrorEvent;
        "focus": FocusEvent;
        "focusin": FocusEvent;
        "focusout": FocusEvent;
        "formdata": FormDataEvent;
        "gotpointercapture": PointerEvent;
        "input": Event;
        "invalid": Event;
        "keydown": KeyboardEvent;
        "keypress": KeyboardEvent;
        "keyup": KeyboardEvent;
        "load": Event;
        "loadeddata": Event;
        "loadedmetadata": Event;
        "loadstart": Event;
        "lostpointercapture": PointerEvent;
        "mousedown": MouseEvent;
        "mouseenter": MouseEvent;
        "mouseleave": MouseEvent;
        "mousemove": MouseEvent;
        "mouseout": MouseEvent;
        "mouseover": MouseEvent;
        "mouseup": MouseEvent;
        "paste": ClipboardEvent;
        "pause": Event;
        "play": Event;
        "playing": Event;
        "pointercancel": PointerEvent;
        "pointerdown": PointerEvent;
        "pointerenter": PointerEvent;
        "pointerleave": PointerEvent;
        "pointermove": PointerEvent;
        "pointerout": PointerEvent;
        "pointerover": PointerEvent;
        "pointerup": PointerEvent;
        "progress": ProgressEvent;
        "ratechange": Event;
        "reset": Event;
        "resize": UIEvent;
        "scroll": Event;
        "scrollend": Event;
        "securitypolicyviolation": SecurityPolicyViolationEvent;
        "seeked": Event;
        "seeking": Event;
        "select": Event;
        "selectionchange": Event;
        "selectstart": Event;
        "slotchange": Event;
        "stalled": Event;
        "submit": SubmitEvent;
        "suspend": Event;
        "timeupdate": Event;
        "toggle": Event;
        "touchcancel": TouchEvent;
        "touchend": TouchEvent;
        "touchmove": TouchEvent;
        "touchstart": TouchEvent;
        "transitioncancel": TransitionEvent;
        "transitionend": TransitionEvent;
        "transitionrun": TransitionEvent;
        "transitionstart": TransitionEvent;
        "volumechange": Event;
        "waiting": Event;
        "webkitanimationend": Event;
        "webkitanimationiteration": Event;
        "webkitanimationstart": Event;
        "webkittransitionend": Event;
        "wheel": WheelEvent;
    }
MouseEvent(鼠标事件)

接口指用户与指针设备(如鼠标)交互时发生的事件

    mousedown | mouseenter | mouseleave | mousemove | mouseout | mouseover | mouseup .. 
PointerEvent(指针事件)

Pointer events 是一类可以为定点设备所触发的DOM事件

    pointerdown | pointercancel | pointerenter | pointerover | pointerup ....

KeyboardEvent (键盘事件)

    keydown | keypress | keyup ... 

ProgressEvent (进度事件)

    progress

ProgressEvent 接受一个target的范型。

    interface ProgressEvent<T extends EventTarget = EventTarget> extends Event {
        /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/ProgressEvent/lengthComputable) */
        readonly lengthComputable: boolean;
        /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/ProgressEvent/loaded) */
        readonly loaded: number;
        readonly target: T | null;
        /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/ProgressEvent/total) */
        readonly total: number;
}

简单举个例子:

    <div @click="clickEvent"></div> 
    <div @change=changeEvent"></div> 
    <div @keyboard="KeyboardEvent" >Keyboard</div>
    
     const KeyboardEvent = (e: KeyboardEvent): void => {}
     const clickEvent = (e: MouseEvent): void => {}
     const changeEvent = (e: Event): void => {}

3. React中事件的类型

常用React的小伙伴可能有不一样的感受,React中的事件类型和常规的事件类型不太一样

这是因为在React中,事件类型是通过合成事件(SyntheticEvent)来定义的,并统一放在React.MouseEventReact.KeyboardEvent等React的内置类型中的。

  1. MouseEvent<T = Element> 鼠标事件对象
  2. TouchEvent<T = Element> 触摸事件对象
  3. ChangeEvent<T = Element> Change 事件对象
  4. ClipboardEvent<T = Element> 剪贴板事件对象
  5. DragEvent<T = Element> 拖拽事件对象
  6. WheelEvent<T = Element> 滚轮事件对象
  7. AnimationEvent<T = Element> 动画事件对象
  8. TransitionEvent<T = Element> 过渡事件对象
const startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {}
const onDragStart = (event: React.DragEvent<HTMLDivElement>: void => {}

在日常的框架开发中,为了兼容类型,最好多使用框架提供的类型声明。

四. 练习一下

简单写一个input框type=file的上传文件读取功能。

    <template>
        <input type="file" @change="changeFile" />
    </template>
    

     const changeFile = (e: Event) => {
            const target = e.target as HTMLInputElement;
            const files = target.files![0];
            let reader = new FileReader();
            reader.readAsDataURL(files);

            reader.addEventListener('load', (e: ProgressEvent<FileReader>) => {
                console.log(e.target!.result)
            })
        }

解析一下:

  • change事件的类型为Event

  • 我们要读取e.target.files[0]的文件上传结果。就需要指定一下e.target(获取的当前元素)元素类型为HTMLInputElement。然后依次去获取files。(这里必须要设置一下e.target为元素类型为HTMLInputElement,不然无法获取到files)

  • ProgressEvent 当开启的对文件读取的操作,就要设置ProgressEvent的具体类型来设置。

五. 结尾

基本的dom类型和event类型都讲解了一下。有兴趣的同学可以补充一下!感谢!!写这个文章,主要就是尽量去在自己写ts的时候约束一下自己。

转载自:https://juejin.cn/post/7274626136327110708
评论
请登录