移除 Ant Design 中默认的 a 元素样式
Ant Design 总是会给 a 元素设置默认样式,这样会导致在使用 a 元素时,样式不符合我们的预期。比如在 a 元素中设置了 color 为 red,但是在 Ant Design 中,a 元素的默认样式是 blue,这样就会导致我们的样式被覆盖。可以用以下方式去除默认样式:
a[href],
a[href]:active,
a[href]:hover {
color: inherit;
}
Ant Design 总是会给 a 元素设置默认样式,这样会导致在使用 a 元素时,样式不符合我们的预期。比如在 a 元素中设置了 color 为 red,但是在 Ant Design 中,a 元素的默认样式是 blue,这样就会导致我们的样式被覆盖。可以用以下方式去除默认样式:
a[href],
a[href]:active,
a[href]:hover {
color: inherit;
}
在使用 Ant Design 的 Form 表单时,经常会遇到这个警告:
Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?
如果确定传递了 form 属性,那么一般是由于调用 form 实例的方法时,Form 表单还没有被渲染的原因,常见于 Modal 中嵌套的 Form,解决办法为监听弹窗是否被打开:
useEffect(() => {
if (!open) return
form.setFieldsValue({})
}, [open, form])
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
提供受控属性 value 或其它与 valuePropName 的值同名的属性。
提供 onChange 事件或 trigger 的值同名的事件。
转发 ref 或者传递 id 属性到 dom 以支持 scrollToField 方法。
import { ChangeEvent, FC, forwardRef } from "react"
import { Button, Form, Input } from "antd"
import { useForm } from "antd/es/form/Form"
import FormItem from "antd/es/form/FormItem"
import { useInputState } from "soda-hooks"
type Info = {
name?: string
id?: string
}
type InfoItemProps = {
value?: Info
onChange?: (value: Info) => void
}
const InfoItem = forwardRef<HTMLDivElement, InfoItemProps>((props, ref) => {
const { value, onChange } = props
// 推荐使用 soda-hooks 的 useInputState
const [name, setName] = useInputState(value?.name)
const [id, setId] = useInputState(value?.id)
function onNameChange(e: ChangeEvent<HTMLInputElement>) {
setName(e.target.value)
// 将变化之后的 value 传递给外部,优先级为 state < props < 最新的变化的 value
onChange?.({ id, ...value, name: e.target.value })
}
function onIdChange(e: ChangeEvent<HTMLInputElement>) {
setId(e.target.value)
onChange?.({ name, ...value, id: e.target.value })
}
return (
<div ref={ref}>
<Input value={name} onChange={onNameChange} />
<Input value={id} onChange={onIdChange} />
</div>
)
})
type FormData = {
info: Info
}
const App: FC = () => {
const [form] = useForm<FormData>()
return (
<Form<FormData> form={form} onFinish={console.dir}>
<FormItem<FormData> name="info">
<InfoItem />
</FormItem>
<FormItem<FormData>>
<Button onClick={() => form.setFieldsValue({ info: undefined })}>Reset</Button>
</FormItem>
<FormItem<FormData>>
<Button htmlType="submit">Submit</Button>
</FormItem>
</Form>
)
}
export default App
onChange 是会一直变化的,所以需要获取到最新值onChange 与 form.setFieldsValue 一样,都是同步的之前一直都是使用 css 来实现必填组件的 * 号的隐藏,没想到 Ant Design 官方提供了修改的方法:
import React, { FC, Fragment, useState } from "react"
import { InfoCircleOutlined } from "@ant-design/icons"
import { Button, Form, Input, Radio, Tag } from "antd"
import { useForm } from "antd/es/form/Form"
import FormItem from "antd/es/form/FormItem"
type RequiredMark = boolean | "optional" | "customize"
const customizeRequiredMark = (label: React.ReactNode, { required }: { required: boolean }) => (
<Fragment>
{required ? <Tag color="error">Required</Tag> : <Tag color="warning">optional</Tag>}
{label}
</Fragment>
)
const App: FC = () => {
const [form] = useForm()
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>("optional")
const onRequiredTypeChange = ({ requiredMarkValue }: { requiredMarkValue: RequiredMark }) => {
setRequiredMarkType(requiredMarkValue)
}
return (
<Form
form={form}
layout="vertical"
initialValues={{ requiredMarkValue: requiredMark }}
onValuesChange={onRequiredTypeChange}
requiredMark={requiredMark === "customize" ? customizeRequiredMark : requiredMark}
>
<FormItem label="Required Mark" name="requiredMarkValue">
<Radio.Group>
<Radio.Button value>Default</Radio.Button>
<Radio.Button value="optional">Optional</Radio.Button>
<Radio.Button value={false}>Hidden</Radio.Button>
<Radio.Button value="customize">Customize</Radio.Button>
</Radio.Group>
</FormItem>
<FormItem label="Field A" required tooltip="This is a required field">
<Input placeholder="input placeholder" />
</FormItem>
<FormItem
label="Field B"
tooltip={{
title: "Tooltip with customize icon",
icon: <InfoCircleOutlined />,
}}
>
<Input placeholder="input placeholder" />
</FormItem>
<FormItem>
<Button type="primary">Submit</Button>
</FormItem>
</Form>
)
}
export default App
requiredMark 有四种可以传递的值:
true 默认值
false 不显示是否必填
"optional" 在可选表单项的 label 后面添加 (可选)
(label: ReactNode, { required }: { required: boolean }) => ReactNode 自定义渲染函数
在 Remix.js 中使用 Ant Design 会出现首次渲染样式丢失的问题,参考 Ant Design 官方的解决方案 整体导出
npm i @ant-design/static-style-extract
npm i cross-env tsx -D
bun i @ant-design/static-style-extract
bun i cross-env tsx -D
pnpm i @ant-design/static-style-extract
pnpm i cross-env tsx -D
yarn add @ant-design/static-style-extract
yarn add cross-env tsx -D
{
"scripts": {
"predev": "tsx ./scripts/genAntdCss.tsx",
"prebuild": "cross-env NODE_ENV=production tsx ./scripts/genAntdCss.tsx"
},
"dependencies": {
"@ant-design/static-style-extract": "^1.0.2"
},
"devDependencies": {
"cross-env": "^7.0.3",
"tsx": "^4.15.6"
}
}
import fs from "fs"
import { extractStyle } from "@ant-design/static-style-extract"
import { ConfigProvider } from "antd"
const outputPath = "./app/antd.min.css"
const css = extractStyle(node => <ConfigProvider theme={{ token: { colorPrimary: "red" } }}>{node}</ConfigProvider>)
fs.writeFileSync(outputPath, css)
import "./antd.min.css"
如果有自定义主题的需求,只需要传递给 ConfigProvider 相应的配置即可:
const css = extractStyle(node => <ConfigProvider theme={{ token: { colorPrimary: "red" } }}>{node}</ConfigProvider>)
这种办法只能是妥协之计,打包出来的 css 文件很大。具体的优化还需要官方实现
Ant Design 的 CSS-in-JS 默认通过 :where 选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式的成本,不过 :where 语法的兼容性在低版本浏览器比较差。在某些场景下你如果需要支持旧版浏览器(或与 TailwindCSS 优先级冲突),你可以使用 @ant-design/cssinjs 取消默认的降权操作(请注意版本保持与 antd 一致):
import { FC } from "react"
import { StyleProvider } from "@ant-design/cssinjs"
// `hashPriority` 默认为 `low`,配置为 `high` 后,
// 会移除 `:where` 选择器封装
const App: FC = () => (
<StyleProvider hashPriority="high">
<MyApp />
</StyleProvider>
)
export default App
切换后,样式将从 :where 切换为类选择器:
:where(.css-bAMboO).ant-btn {
}
.css-bAMboO.ant-btn {
color: #fff;
}