如何正确键入 define event.target.value?

How to properly type define event.target.value?

我正在创作一个节点包,但我的 定义有点问题。更具体地说 我发现 event.target.value 的定义超级混乱

问题描述:

我有以下事件处理程序:

import { ChangeEvent, useState } from 'react' 

type FieldEvent = ChangeEvent<HTMLInputElement | HTMLTextAreaElement>

export const useField = <T>(input: T) => {
  const [value, setValue] = useState<T>(input)

  const handleChange = (event: FieldEvent) => {
    const { name, value: eventValue } = event.target
    // @ts-expect-error
    setValue(eventValue)
  }

  return [input, handleChange]
}

表达式 setValue(eventValue) 导致以下错误:

Argument of type 'string' is not assignable to parameter of type 'SetStateAction<T>'.

我对此感到有点惊讶,因为很多导出的组件使用不同的 event.target.value。例如日期选择器 return Date 类型,select Object

问题调查

当然我去检查导入的 ChangeEvent react exports 看看它是否有正确的定义,但这似乎是正确的

interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
    target: EventTarget & T;
}

所以根据这个定义,它应该继承传递给 SyntheticEvent

Element 的类型

所以我沿着链条找到位于 node_modules/typescript/lib/lib.dom.d.tsHTMLInputElement 声明,这就是问题的症结所在

interface HTMLInputElement extends HTMLElement {
  value: string
  //... rest
}

我回头检查了一下,似乎所有原生 <input> 元素都默认使用 string 作为它们的值类型,我想这是有道理的。

解决问题

显然这并不理想,因为这并不代表 event.target.value 许多使用第三方包的 项目中的行为 (我的包应该支持)。考虑以下代码和框

returned event.target.value 与您对 typeof number

的期望一样

这让我想到了一个问题,我是否应该简单地用以下定义覆盖 ChangeEvent

ChangeEvent<{ value: T, name: string } & HTMLInputElement>

或者这会被视为不好的做法吗?或者是否有更好的方法来完全做到这一点?

handleChange 与要求的参数不匹配。

我已经尝试过并且有效:

export default function App() {
  const [selected, setSelected] = useState(1);
  const handleChange = (e: ChangeEvent<{
    name?: string | undefined,
    value: unknown | number
  }>, child: React.ReactNode) => {
    setSelected(e.target.value as number);
  };

  return (
    <Select value={selected} onChange={handleChange}>
      <MenuItem value={1}>One</MenuItem>
      <MenuItem value={2}>Two</MenuItem>
      <MenuItem value={3}>Three</MenuItem>
    </Select>
  );
}

好吧,我不是 100% 确定这是否是正确的方法,但它似乎适合我的 use-case,尽管输入看起来有点奇怪,但基本上我正在覆盖将类型参数传递给 ChangeEvent 并通过 HTML 元素之一的并集对其进行扩展。

export type FieldEvent<T> = ChangeEvent<
 { value: T, name?: string } &
 (HTMLInputElement | HtmlTextAreaElement | HTMLSelectElement)
>

这会覆盖 ChangeEvent 的类型定义,然后您只需要创建一个扩展类型参数的处理函数

export type FieldHanderFunction<T> = (event: FieldEvent<T>) => void

所以在我的钩子里,它基本上归结为:

const useField<T> = (input: T) => {
 const handleChange = (event: FieldEvent<T>) => {
  // ...
 }
}