tailwindcss现在用的人越来越多了,深度使用后才知道有多香。但是有个烦恼困扰过我很久,不知道小伙伴们有没有遇到过,那就是组件库里的一些样式,如果不支持传入class去修改的话,那么会如果覆盖组件库的基础样式将是一个巨大的困扰。
当然最简单的方法就是再加个css文件,用css的方式去覆盖组件库的样式。
举个例子:
假设我基于 antd 的 card 封装了这么一个 React 组件
import { Card } from 'antd';
import classNames from 'classnames';
import React, { HTMLProps } from 'react';
export interface MyCardProps extends HTMLProps<HTMLDivElement> {}
export const MyCard = (props: MyCardProps) => {
const { className = '', ...restProps } = props;
return (
<div className={classNames('flex items-center justify-center', className)} {...restProps}>
<Card title="测试样式覆盖" extra={<a href="#">More</a>} className="w-[500px]" hoverable>
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
<div className="text-orange-300">特殊的 line</div>
</Card>
</div>
);
};
可以看到这个组件封装的很糟糕,只暴露了最外层的className给外部使用。
此时组件渲染出来长这样:
好的现在我们要在调用MyCard组件的地方把卡片的 title 改成红色。
使用css进行覆盖:
这个时候常见的做法是引入一个css文件,直接
import './index.css'
<MyCard className={classNames('my-card')} />
因为antd中title的class是 ant-card-head-title
所以我们在css文件中可以写下:
.my-card .ant-card-head-title {
color: red;
}
这样就顺利完成了样式覆盖。这样太麻烦了,我都用了tailwindcss你还要我用css,那又要考虑css起名这个千古难题,还有命名冲突或者用 css module 来进行样式隔离。
那有没有更好的办法呢?
使用 tailwindcss 进行组件库样式的覆盖
matchVariant 函数
tailwindcss 官方是提供了解决办法的,答案就在 tailwindcss 的插件里的 matchVariant 函数里
关于matchVariant的官方示例可以参考:Plugins - Tailwind CSS
可以看到matchVariant接受三个参数:
- 指令名称——字符串
- 指令的转换函数——函数
- 配置对象(可选)
简单示例
于是我们在 tailwind.config.js 的plugin中添加一个plugin:
plugins: [
plugin(function ({ addUtilities, addComponents, e, config, matchVariant }) {
matchVariant('rp', (value) => {
console.log('replace value ====', value); // 你可以打印一下看看编译的时候,收到的是什么值
return `& ${value)}`;
});
})
]
函数很简单,我们的自定义指定是 rp
,就是replace的简写,然后返回 `& ${value)}`
。
这时候我们去写代码的时候就会发现有提示了(前提是你安装了tailwindcss的插件),如果没有 reload 一下你的vsCode。
要把 MyCard 组件的标题改成红色,这么写就好了
<MyCard className={classNames('rp-[.ant-card-head-title]:text-red-500')} />
然后看看效果。
为什么可以生效
我们把鼠标hover到刚才写的class上看看编译出来的是个什么东西?
就是下面这个东西:
#root :is(.rp-[.ant-card-head-title]:text-red-500 .ant-card-head-title) {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
#root
是我在 tailwind.config.js 里加的import,来提升权重,
:is(.rp-[.ant-card-head-title]:text-red-500 .ant-card-head-title)
is是伪类选择函数,其实这里用不用is是一样的,不用纠结这个。可以看到这里就是和我们上面自己用css写样式覆盖的原理是一样的。
ps:is的精髓是里面的元素用逗号分隔,形成一个或的关系,参考::is() - CSS:层叠样式表 | MDN (mozilla.org)
这样我们就简单的实现了针对某个样式的样式覆盖。
但是问题又来了,我想把最后一行的div加个背景色,该怎么办?
如果我们直接用刚才的方式写下 rp-[.ant-card-body div]:bg-orange-200
是不生效的。
因为这个空格在 tailwindcss 里是解析不了的,但是我们最后编译出来的需要有个空格的,所以我们可以把刚才的plugin改一下,使用下划线当空格。
plugins: [
plugin(function ({ addUtilities, addComponents, e, config, matchVariant }) {
matchVariant('rp', (value) => {
console.log('replace value ====', value); // 你可以打印一下看看编译的时候,收到的是什么值
return `& ${value.replace(/_/g, ' ')}`;
});
})
]
这样就可以正确生效了。
and more:
插件还有很多强大的功能,比如 addUtilities
加个自定义的class类名。
plugins: [
plugin(function ({ addUtilities, addComponents, e, config, matchVariant }) {
// Add your custom styles here
console.log('my plugin ====');
addUtilities({
'.inline-box-border-gray': {
'box-shadow': '0 0 0 2px inset gray'
}
});
matchVariant('rp', (value) => {
console.log('replace value ====', value);
return `& ${value.replace(/_/g, ' ')}`;
});
})
]
上面的 addUtilities
我增加了一个自定了class inline-box-border-gray