为啥允许使用 kebab-case 非标准属性,而不允许使用其他属性?以及如何在 TypeScript 中定义这样的类型?
Posted
技术标签:
【中文标题】为啥允许使用 kebab-case 非标准属性,而不允许使用其他属性?以及如何在 TypeScript 中定义这样的类型?【英文标题】:Why kebab-case non-standard attributes are allowed while others aren't? And how to define types like this in TypeScript?为什么允许使用 kebab-case 非标准属性,而不允许使用其他属性?以及如何在 TypeScript 中定义这样的类型? 【发布时间】:2021-08-12 20:35:53 【问题描述】:使用foo
作为属性会引发错误:
// App.tsx
// ???? throws
const App = () => <div foo></div>
export default App
Type ' foo: true; ' is not assignable to type 'DetailedhtmlProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.
Property 'foo' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.ts(2322)
但是使用foo-foo
没问题,这是为什么呢?
// App.tsx
// ???? no error is thrown
const App = () => <div foo-foo></div>
export default App
最重要的是,如何在 TypeScript 中定义这样的类型?即只允许标准或 kebab-case 属性。
【问题讨论】:
有趣的是,我已经看到将 kebab case 道具添加到元素以进行测试是一种常见的做法。通常像< element data-test="Div" />
这样你就可以找到这个带有 Enzyme 或 React 测试库的数据测试标签。我从来没想过要问为什么kebab case不会导致元素出错。
【参考方案1】:
答案在JSX section of the Typescript Handbook:
如果属性名称不是有效的JS identifier(如
data-*
属性),则如果在元素属性类型中找不到它,则不会将其视为错误。
关于 JSX 的整个部分是一本非常有启发性的读物。
恐怕“如何在 TypeScript 中定义这样的类型”问题的答案是......我们没有。 JSX 支持内置在编译器中。不过,我们可以自定义很多有趣的东西。
【讨论】:
【参考方案2】:法比奥的答案中的 TS 手册说明解释了所有这些,只是想稍微扩展一下。简而言之,kebab-case 属性不被 TS 认为有效但不会抛出错误;但以data-
或aria-
为前缀的属性被认为是有效的。
React (since 16) 接受自定义属性,即 <div foo />
和 <div whateverYouLike=2>
应该可以工作。
我对 React 感到困惑的是,data-*
和 aria-*
应该按原样编写,而不是像其他所有内容一样将它们转换为 camelCase。尤其是当这些属性在 vanilla DOM 中转换为 camelCase 时:
<div data-my-age="100" aria-label="A Test" />
const $div = document.querySelector('#test')
$div.dataset.myName = "D"
console.log( dataset: $div.dataset ) // myAge: "100", myName: "D"
console.log($div.ariaLabel) // "A Test"
没有给出任何理由,所以我们只能推测。也许与 a11y 工具、解析便利等有关。
<div foo />
在 TS 中抛出的原因是因为 TS 提供了一组严格的有效属性名称。但是,正如另一个答案所指出的,TS 不会在 random-foo
上抛出错误,因为它被认为是无效的 JS 标识符。我的猜测是因为 DOM 元素允许任意属性,所以这是一种折衷方案,允许在 TS 中的大多数情况下正确键入,但提供某种逃生舱口。很想知道这些决定背后的原因。
如何在 TypeScript 中定义这样的类型?即只允许标准或 kebab-case 属性。
正如 Fabio 已经指出的,JSX 支持内置在编译器中。然而,除了能够识别构成有效属性名称的能力之外,我认为它没有什么神奇之处:有一个有效 DOM 属性的综合列表。如果您混合使用 kebab 和 camel case,TS 不会抛出错误,即 <div data-myName>
工作,<div myName/>
没有,等等,所以它也不会因大小写而有所不同。
如果你事先知道所有有效的道具,你可以模仿同样的事情。
// allow only these prop names, which happened to be all camelCased
interface MyThing
name: string
myName: string
anotherProp: string
在 kebab-case 的情况下,模板文字类型可能会有所帮助:
type ValidPrefix = "data" | "aria";
type ValidSuffix = "banana" | "apple" | "pear";
type ComputedProps =
[key in `$ValidPrefix-$ValidSuffix`]?: string;
;
const x: ComputedProps =
"data-apple": 'hi'
;
除此之外,TS 中目前没有机制可以区分 camelCase 和 kebab-case 字符串。
如果您正在寻找一种方法来增强 JSX 以允许自定义道具和自定义元素,这是一种方法:
Augmenting JSX attribute to allow custom props & custom elements
【讨论】:
以上是关于为啥允许使用 kebab-case 非标准属性,而不允许使用其他属性?以及如何在 TypeScript 中定义这样的类型?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 C# 6.0 在使用 Null 传播运算符时不允许设置非 null 可空结构的属性?
[Effective JavaScript 笔记]第29条:避免使用非标准的栈检查属性