// 表示宽度 200px w-[200px]在有的道友的认知里,认为 tailwindcss 是一种高度封装的结果,他就应该拿来即用,快速提高我的开发效率,又能保持良好的可读性和可维护性。这肯定是很难做到的。它提供的只是基础能力,任何样式内容一旦变多,就必然会导致可读性的下降。这样的想法让许多道友无法 get 到 tailwindcss 的优点。
备注:好消息是下一个大版本 tailwindcss 4 简化了自定义 class 的配置,它更接近于直接在 css 文件中写样式,而不是在一个工程化的配置文件中写插件。
因此,我们需要把 tailwindcss 提供的方式当成一种基于 css,但是又区别于 css 的一种新的基础能力去看待。我们要从另外一个角度去思考,在这样的基础能力之下,我们应该如何在项目中使用它。有的道友比较欠缺这样的思维方式,因此它写的 css 代码其实也不具备可读性和可维护性。
.center { display: flex; justify-content: center; align-items: center; }在 scss 或者 less 中,我们可以使用继承的方式来复用它
.box { @extend .center; border: 1px solid red; }那么在 tailwindcss 中,你可以将其处理成一个插件,这样做的好处是在使用时可以被 tailwindcss 提供的智能提示插件捕获,减少记忆负担
plugin(({addComponents, theme}) => { addComponents({ '.center': { display: 'flex', justifyContent: 'center', alignItems: 'center', } }) }),例如我封装的一个 .card
.center { @apply flex items-center justify-center; }
const base = 'flex items-center flex-row'由于默认是横向的,但是我希望能够简单的传入属性支持纵向布局,那么我就可以约定一个参数传入 col=true 使用如下。
<Flex col></Flex>我在思考这个命名的时候,有考虑过到底是封装成这种有明确语义的单词来表达横向和纵向。
<Col></Col> <Row></Row>还是这种约定的方式来表达。纠结的原因是因为对 Col 和 Row 这两个单词不是很喜欢,所以最后决定使用这种约定式的方式。Flex 默认为横向,项目中 70% 的地方都是这么用的,少数情况下会使用到纵向,所以通过参数的传入来解决。
<Flex></Flex> <Flex col></Flex>这里还有一个重要的考虑。受到 unocss 的启发,我现在传参比较喜欢第二种简洁的表达方式
<Flex direction='col'></Flex> <Flex col></Flex>这里的一个技巧是把入参处理成布尔类型,然后在组件内部做判断。除此之外,还有对齐方式我们需要处理。在交叉轴的对齐方式上,我们 90% 的场景都可以处理成居中,因此在参数的考虑上,我就不在特别为了交叉轴而设计参数。但是在主轴上的使用变化就比较多,因此针对于每一个值,我都设计了参数
const { children, start, end, around, between, className, center, col, ...other } = props这里的 start, end, around, between, center 都是主轴的布局对应的值。
import clsx from 'clsx' // 堆代码 duidaima.com // 默认方向为 Row export default function Flex(props) { const {children, start, end, around, between, className, center, col, ...other} = props const base = 'flex items-center flex-row' const cls = clsx(base, { ['flex-col']: col, ['justify-start']: start, ['justify-end']: end, ['justify-around']: around, ['justify-between']: between, ['justify-center']: center, }, className) return ( <div className={cls}>{children}</div> ) }这里还有一个非常重要的设计。那就是我还默认把 className 传入进来了。他之所以重要是因为在 tailwindcss 的机制之下,有了这个传入,我可以渐进式补全我的组件封装。
<!-- 使用时的写法也会非常简洁 --> <Flex between className="items-end"></Flex>此时,比如我还想支持以 bg 开头的背景颜色,但是在 tailwindcss 中,支持的背景颜色太多了,我如果想要用同样的方式来处理肯定是不行的,那么怎么办呢?
<Flex bg-green-200></Flex>要支持上面这种写法,这里有一个非常巧妙的技巧,那就是我们可以遍历传入的参数,然后识别 key 值将其转化为正常的 className 即可。例如这里,我定义一个字符串用于接收背景相关的属性,然后遍历 props 并识别出以 bg- 开头的属性。
let bgclass = '' Object.keys(other).forEach(key => { if (key.indexOf('bg-') === 0) { bgclass += `${key} ` } })这种方式可以发挥的想象空间非常大,例如,我这里处理了背景、边框、圆角相关的属性,完整的代码如下
import clsx from 'clsx' import {twMerge} from 'tailwind-merge' // 默认方向为 Row export default function Flex(props) { const {children, start, end, around, between, className, center, col, ...other} = props const base = 'flex items-center flex-row' let bgclass = '' let borderclass = '' let roundclass = '' Object.keys(other).forEach(key => { if (key.indexOf('bg-') === 0) { bgclass += `${key} ` } if (key.indexOf('border') === 0) { borderclass += `${key} ` } if (key.indexOf('rounded') === 0) { roundclass += `${key} ` } }) const cls = clsx(base, { ['flex-col']: col, ['justify-start']: start, ['justify-end']: end, ['justify-around']: around, ['justify-between']: between, ['justify-center']: center, }, bgclass, borderclass, roundclass, className) return ( <div className={twMerge(cls)} {...other}>{children}</div> ) }然后使用结果就变得非常简洁
<Flex between col bg-green-200 border border-green-600 rounded> <div>1</div> <div>1</div> <div>1</div> <div>1</div> </Flex>演示效果如图
w-[200px]我们就可以通过这种思路的转化,变成如下的方式传入
<Flex w-200></Flex>进一步优化
function TailwindDiv(props) { const {className, children, ...other} = props let bgclass = '' let borderclass = 'border' let roundclass = 'rounded' Object.keys(other).forEach(key => { if (key.indexOf('bg-') === 0) { bgclass += `${key} ` } if (key.indexOf('border-') === 0) { borderclass += ` ${key} ` } if (key.indexOf('rounded-') === 0) { roundclass += ` ${key} ` } }) const cls = clsx(bgclass, borderclass, roundclass, className) return ( <div className={twMerge(cls)} {...other}>{children}</div> ) }那么 Flex 的封装就可以简化一下,只关注自身语义相关的逻辑即可。
import clsx from 'clsx' import {twMerge} from 'tailwind-merge' // 默认方向为 Row export default function Flex(props) { const {children, start, end, around, between, className, center, col, ...other} = props const base = 'flex items-center flex-row' const cls = clsx(base, { ['flex-col']: col, ['justify-start']: start, ['justify-end']: end, ['justify-around']: around, ['justify-between']: between, ['justify-center']: center, }, className) return ( <TailwindDiv className={twMerge(cls)} {...other}>{children}</TailwindDiv> ) }最终的使用结果如下
<Flex col between bg-green-200 border-green-600> <div>1</div> <div>1</div> <div>1</div> <div>1</div> </Flex>
<!--直接使用--> <div className='flex items-center justify-between flex-col border border-green-600 bg-green-200 rounded'> <div>1</div> <div>1</div> <div>1</div> <div>1</div> </div>