你应该在
HTML
中使用a
标签、button
标签,还是其他标签(例如div
)来创建可点击的元素呢?
// 🚩
export function MyButton() {
return <div onClick={...}>Click me</div>
}
//❓
export function MyButton() {
return <button onClick={...}>Click me</button>
}
//❓
export function MyButton() {
return <a onClick={...}>Click me</a>
答案比你想象的更复杂.
div标签的问题
首先明确一点——你不应该使用div
作为可点击的元素(至少在99%的情况下)。为什么呢?
简单来说,div != button
。div
只是一个通用的容器,缺乏一个真正的可点击元素应该具备的一些特性,例如:
-
div
不可聚焦,例如你的Tab键不会像对其他按钮那样聚焦到div。 - 屏幕阅读器和其他辅助设备不将
div
识别为可点击的元素。 -
div
在聚焦时不会将某些按键输入(如空格键或回车键)转换为点击。
你可以通过一些属性来解决这些问题,例如tabindex="0"
和role="button"
:
//🚩 Trying to make a div *mostly* behave like a button...
export function MyButton() {
function onClick() { ... }
return (
<div
className="my-button"
tabindex="0" // 使div可聚焦
role="button" // 提示屏幕阅读器这是可点击的
onClick={onClick}
onKeydown={(event) => {
// 在聚焦时监听回车和空格键,并手动调用点击处理程序
if (event.key === "Enter" || event.key === "Space") {
onClick()
}
}}
>
Click me
</div>
)
}
哦,对了,我们还需要确保在聚焦状态下进行样式处理,以便用户知道该元素已被聚焦。同时,我们必须确保这满足所有的无障碍要求,例如:
.my-button:focus-visible {
outline: 1px solid blue;
}
这是一项艰巨的工作,我们需要努力找出所有细微但关键的按钮行为,并手动执行它们。
但幸运的是,有一个更好的方法(大多数情况下)!
按钮标签的救赎
按钮标签的美妙之处在于,它就像你设备上的其他按钮一样工作,并且正是用户和无障碍工具所期望的。
它是可聚焦的、可访问的、支持键盘输入的,有合规的聚焦状态样式,功能齐全!
// ✅
export function MyButton() {
return (
<button onClick={...}>
Click me
</button>
)
}
但按钮也有一些问题需要注意。
button标签拯救了我们
对按钮最大的抱怨是样式处理。
例如,如果你只是想给按钮一个漂亮的浅紫色背景:
<button class="my-button">
Click me
</button>
<style>
/* 🤢 */
.my-button {
background-color: purple;
}
</style>
你会得到这样一个丑陋的按钮:
image.png
看起来像是从Windows 95中出来的一样。
是的,浏览器会对按钮元素强制应用各种奇怪的样式,而应用你自己的样式只会让情况更糟。
这就是为什么我们都喜欢div。它们没有任何多余的样式或行为负担。它们总是按照预期的方式工作和显示。
现在你可能会说,哦!appearance: none会重置外观!但实际上,它并不能完全达到你的预期。
<button class="my-button">
Click me
</button>
<style>
.my-button {
appearance: none; /* 🤔 */
background-color: purple;
}
</style>
我们的怪物仍然存在:
image.png
修正按钮样式
没错,我们实际上必须逐行重置属性:
/* ✅ */
button {
padding: 0;
border: none;
outline: none;
font: inherit;
color: inherit;
background: none;
}
这最终会给我们一个看起来和行为都像div的按钮,但有一个额外的好处,它仍然使用浏览器的默认聚焦样式。
另一个选择是使用all: unset
来通过一个简单的属性恢复到没有特殊样式的状态:
/* ✅ */
button { all: unset; }
button:focus-visible { outline: 1px solid var(--your-color); }
别忘了添加你自己的聚焦状态;例如,使用与你品牌颜色对比度足够的轮廓——这样就可以了。
修复按钮表单行为
使用按钮标签时,还有一个问题需要注意。表单中的任何按钮默认会被视为提交按钮,点击时会提交表单。什么?
function MyForm() {
return (
<form onSubmit={...}>
...
<button type="submit">Submit</button>
{/* 🚩 点击“Cancel”也会提交表单! */}
<button onClick={...}>Cancel</button>
</form>
)
}
没错,按钮的默认type
属性是submit
。是的,这很奇怪,也很烦人。
为了解决这个问题,除非你的按钮确实是用于提交表单的,否则请务必添加type="button"
,如下所示:
export function MyButton() {
return (
<button
type="button" // ✅
onClick={...}>
Click me
</button>
)
}
现在,我们的按钮将不再尝试找到最近的表单父元素并提交它。呼,差点儿出问题。
链接到其他页面
这里有一个我们规则的重大例外。我们不想使用按钮来链接到其他页面:
// 🚩
function MyLink() {
return (
<button
type="button"
onClick={() => {
location.href = "/"
}}
>
Don't do this
</button>
)
}
使用点击事件链接到页面的按钮有几个问题:
- 它们不可爬取,因此对SEO非常不利。
- 用户无法在新标签或窗口中打开此链接,例如,不能通过cmd/ctrl点击,右键单击选择“在新标签中打开”。
因此,不要使用按钮进行导航。这时候我们要用到可靠的a标签。
// ✅
function MyLink() {
return (
<a href="/">
Do this for links
</a>
)
}
最棒的是,它们具有上述所有按钮的优点——可访问、可聚焦、支持键盘输入——而且没有一堆奇怪的样式!
但是,在你说“等等,如果a标签有按钮的优点,没有奇怪的样式,我们是不是应该用它们来处理所有可点击的东西,省去一些麻烦?”之前……
// 🚩
function MyButton() {
return (
<a onClick={...}>
Do this for links
</a>
)
}
实际上,不行。这是因为没有href
的a标签不再像按钮一样工作。没错,只有当a标签有一个带值的href
时,它才具备完整的按钮行为,如可聚焦。
所以,让我们确保按钮就是按钮,锚链接就是链接。
综合起来
我喜欢的一个模式是将这些规则封装在一个组件中,这样你只需使用MyButton
组件,如果你提供了一个URL
,它就变成一个链接,否则就是一个按钮,如下所示:
// ✅
function MyButton(props) {
if (props.href) {
return <a className="my-button" {...props} />
}
return <button type="button" className="my-button" {...props} />
}
// 渲染一个 <a href="/">
<MyButton href="/">Click me</MyButton>
// 渲染一个 <button type="button">
<MyButton onClick={...}>Click me</MyButton>
这样,我们可以有一致的开发体验和用户体验,无论按钮的目的是点击处理程序还是链接到另一个页面。
最后,让我们添加一些类型:
type AnchorProps = React.AnchorHTMLAttributes<HTMLElement>
type ButtonProps = React.ButtonHTMLAttributes<HTMLElement>
type MyButtonProps = AnchorProps | ButtonProps
function isAnchor(props: MyButtonProps): props is AnchorProps {
return (props as AnchorProps).href !== undefined
}
export function MyButton(props: MyButtonProps) {
if (isAnchor(props)) {
return <a className="my-button" {...props} />
}
return <button type="button" className="my-button" {...props} />
}
简而言之:对于链接,使用带有href
属性的锚标签;对于所有其他按钮,使用带有type="button"
的按钮标签。
网友评论