TypeScript - 'Element'不可赋值给HTMLInputElement

10

我正在使用TypeScript和React。在我的组件中,它是一个对话框窗口,我想将触发元素(例如按钮)存储为属性。当组件卸载时,我希望将焦点返回到该元素。然而,我收到了一个来自TSLint的错误,我不确定如何解决。

class Dialog extends React.Component<Props, {}> {
  ...
  focusedElementBeforeDialogOpened: HTMLInputElement;

  componentDidMount() {
    this.focusedElementBeforeDialogOpened = document.activeElement;
    ...
  }
  ...
}

我在为属性赋值的那一行收到了错误提示:
[ts] Type 'Element' is not assignable to type 'HTMLInputElement'.
  Property 'accept' is missing in type 'Element'.

然而,如果我将属性的类型更改为Element或甚至是HTMLInputElement,在componentWillUnmount()中会收到错误信息。

componentWillUnmount() {
    ...
    this.focusedElementBeforeDialogOpened.focus();
}

这个错误是指元素类型没有focus()方法。
问题:
有没有办法告诉TypeScript document.activeElement应该是输入类型?类似于 this.focusedElementBeforeDialogOpened = <HTMLInputElement>document.activeElement; 或者有更好的方法来声明既支持document.activeElement又支持.focus()的类型吗?

我不熟悉React,但在Angular中,您有nativeElement属性。在activeElement之后难道没有吗? - user4676340
2个回答

5

根据document.activeElement的文档:

通常,如果在此时具有文本选择,则返回一个<input><textarea>对象。 如果是这样,您可以使用元素的selectionStart和selectionEnd属性获取更多详细信息。 其他时候,聚焦的元素可能是菜单(<select>元素)或<input>元素,类型为button、checkbox或radio。

也就是说,document.activeElement不一定是HTMLInputElement的实例(它也可以是HTMLSelectElement等其他类型)。

如果确实只想查找HTMLInputElement,则可以使用一个简单的instanceof类型保护,TypeScript会识别:

componentDidMount() {
    if (document.activeElement instanceof HTMLInputElement) {
        this.focusedElementBeforeDialogOpened = document.activeElement;
    }
    ...
}

此外,您可以检测元素是否具有焦点,例如通过定义自己的类型守卫:

此外,您可以通过定义自己的类型守卫来检测元素是否可获得焦点:

interface IFocusableElement {
    focus(): void;
}

function isFocusable(element: any): element is IFocusableElement {
    return (typeof element.focus === "function");
}

然后使用IFocusableElement作为this.focusedElementBeforeDialogOpened的类型,并编写自己的类型保护:

focusedElementBeforeDialogOpened: IFocusableElement;

componentDidMount() {
    if (document.activeElement && isFocusable(document.activeElement)) {
        this.focusedElementBeforeDialogOpened = document.activeElement;
    }
    ...
}

如果您还需要Element提供的原始API,您可以使用交叉类型:

focusedElementBeforeDialogOpened: IFocusableElement & Element;

由于HTMLInputElement似乎只返回<input type ='button'>,因此是否建议将HTMLButtonElement添加到可能的类型和类型保护中以支持<button>元素? - Yuschick
这个解决方案有一些优雅的部分,但并不完整,因为当document.activeElement没有focus方法时,componentWillUnmount()可能会在undefined上调用this.focusedElementBeforeDialogOpened.focus(); - Romain Deneau

1

实际上,lib.es6.d.ts 中的定义有点不一致,除非是 W3C 规范有点令人困惑:

interface Document extends [...] {
    /**
     * Gets the object that has the focus when the parent document has focus.
     */
    readonly activeElement: Element;
    [...]
    focus(): void;
    [...]
}

interface HTMLElement extends Element {
    [...]
    focus(): void;
    [...]
}

所以,我们只能手动聚焦到DocumentHtmlElement这两种对象,但是任何Element都可以被聚焦!如果你只需要返回焦点,可以像这样做:
class Dialog extends React.Component<Props, {}> {
    private resetFocus = () => {};

    componentDidMount() {
        const elementToFocus = document.activeElement instanceof HTMLElement
            ? document.activeElement
            : document;
        this.resetFocus = () => elementToFocus.focus();
    }

    componentWillUnmount() {
        this.resetFocus();
    }
}

或者

componentDidMount() {
    const activeElement = document.activeElement as any;
    if (typeof activeElement.focus === 'function') {
        this.resetFocus = () => activeElement.focus();
    }
}

谢谢提供的一些文档。将焦点让回去并不是我的关注点。我正在努力寻找如何避免使用“any”类型。 - Yuschick
我刚刚编辑了代码以删除不必要的 any。它可能更适合你的需求^^ - Romain Deneau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接