龙空技术网

如何自己实现一个原子化样式类库?

程序员詹姆斯 63

前言:

现在兄弟们对“行内样式的优缺点”大概比较重视,同学们都想要剖析一些“行内样式的优缺点”的相关内容。那么小编也在网上汇集了一些对于“行内样式的优缺点””的相关资讯,希望各位老铁们能喜欢,同学们一起来了解一下吧!

从最原初的CSS,再到 sass,less,stylus,等各种CSS预处理工具,再到现在的css-in-js, atom css,css在web发展的各个时期都在经历各种演变,最终的目的还是为了让css更加方便书写,开发更加简单,更加符合前端模块化,工程化的演变。到如今的原子化样式类.都是在向这一目标演进,而原子化样式提高了复用性,只不过有的人可能觉得演变来演变去,到最后都成了行内样式了。

但是,原子化样式类并未成为一个行内样式,因为其复用性要比行内样式高,行内样式只能针对于单一元素及其子元素起到作用,但是原子化样式类则是对所有引用到这个样式类的元素都有效果。虽然看上去最终样式写了一堆,但是都只是在重复的使用某一个样式而已,并没有增加样式的体积。所以我们只需要极少的样式类就能写出复杂的页面。

<div class="h-fit p-3 my-3">testing testing</div>

比如上面的代码,写经过一些原子化类库预处理之后,只会生成3个样式,并且还可以复用在其他元素或者是组件上,并不会导致只能应用在上面的div的疑虑。下面是最终生成的样式

.h-fit {height: fit-content;}.p-3 {padding: 0.75rem;}.my-3 {margin-top: 0.75rem;margin-bottom: 0.75rem;}

但是,虽然原子化样式类写起来很舒服,但是如果需要后期维护,那就不亚于上刑场了。主要是由于原子化样式类丢失了样式语义,从而失去了可阅读性。让不懂的人无异于在阅读天书.

既然说完了原子化样式类的优劣。那么,或许是该来实现一下如何写一个原子化样式类库了。让我们看看具体的原理到底是怎样的。

我们可以将 h-fit p-3 my-3 视作一系列规则或者说是某种语言,我们需要实现一个这个规则到css的转换器。我们可以将这段规则视为某种代码,然后去实现一个函数,参数的参数就是输入这些代码,输出则是具体的css.像这样:

generator(`h-fit p-3 my-3`)// 然后函数则会生成css

当然,我们还得考虑一些border case,比如样式像 w-[300px] w-30% bg-[#fff] 这类样式类,其中的符号在css中是有特殊意义的,所以我们需要将这些符号做一下转义。使其变为普通字符。所以有如下代码

export const generatorClss = (className: string) => {for (let char of className) {if (['[', ']', "#", ":", "(", ")", ",", "/","!","%","@"].includes(char)) {className = className.replace(char, `\\${char}`)}}return `.${className}`}

在将这些样式特殊字符转义的问题解决了,那接下来就是要考虑该如何实现样式的生成了。关于规则的匹配,我就使用正则。每一个规则是唯一的,不会出现重复的情况。所以设计一个下面的函数,用来处理规则的匹配以及后续的处理问题。

function variant() {let rules = new Map<RegExp, (...args: any[]) => string>()let appendRule = (rule: RegExp, cb: (...args: any[]) => string) => {rules.set(rule, cb)}let generator = (text: string) => {for (const [rule, gen] of rules) {let match = rule.exec(text)if (rule.test(text) && match && match[0] === text) {return variantMatch(text, rule, gen)}}}return {append: appendRule,generator,rules}}

好了,准备工作完成,我们只需要不断的往rules这个map里面添加规则就行了。于是就有了如下的代码:

const { append } = variant()append(/(m|margin)-\[(.+)\]/, (className, _, rule) => {return `${generatorClss(className)}{margin:${rule};}`})append(/(m|margin)(x|y)-(\d+)/, (className, _, direction: "x" | "y", size: `${number}`) => {const directionMap = {"x": `margin-left: {size};margin-right: {size};`,"y": `margin-top: {size};margin-bottom:{size};`}const marginSize = parseInt(size)return `${generatorClss(className)}{${template(directionMap[direction], {size: `${marginSize * 0.25}rem`})}}`})

这样就完成了关于margin的原子化样式规则的css生成了。还有上面的其他的规则,代码如下:

append(/(h|height)-(\d+)/, (className, rule) => {let width = parseInt(rule) * 0.25return `${generatorClss(className)}{height:${width}rem;}`})append(/(h|height)-(min|max|fit)/,(className,_,rule)=>{return `${generatorClss(className)}{height: ${rule}-content;}`})append(/(h|height)-(\d+\/\d+)/,(className,_,rule)=>{let result = (rule.split("/").map(Number) as number[]).reduce((a:number, b:number) => a / b).toFixed(6);return `${generatorClss(className)}{height:${parseFloat(result)*100}%;}`})

其他的规则想必在有上面的代码示例之后,读者自己也能编写出来,在这里我就不继续展示代码该如何生成了. 我这里编写的原子化样式类库只作为一个学习示例,所以不去考虑扫描js,jsx,php,tsx,vue等文件的问题,只作为一个运行时的原子化样式类库。所以缩小问题范围,只考虑该如何获取到页面中的类以及生成。当然这里要考虑的问题就是原子化样式类每种样式类只生成一次,避免像自己写样式那样同样的样式重复写,需要提高复用率。所以会有一个去重问题。我们可以用hash table,即map来完成去重,将原子样式类作为key,如果有重复只会覆盖掉原有的,不会新增。

当然,也有更加简便的方法,也就是Set,Set也有去重的效果。下面是代码

const scanPage =()=>{const classes = new Set<string>();document.querySelectorAll("*[class]").forEach((ele)=>{ele.classList.forEach((cls)=>{classes.add(cls)})})return [...classes]}

我们使用querySelectorAll方法获取所有有class属性的元素,将这些元素中的类全部都添加到Set当中。最终我们就得到了一个完全没有重复项的原子化样式类名的集合。

document.onreadystatechange = function(){if(document.readyState === "complete"){let styled = document.createElement("style")let atomClass = scanPage()console.debug(`当前使用了${atomClass.length}条规则`)styled.setAttribute("type","text/css")styled.textContent = variantGenerator(atomClass.join(" ")).replace(/\s/g,"")document.head.appendChild(styled)console.debug(`当前总计规则数为: ${globalVariant.rules.size}条规则`)}}

在这里,我使用文档对象的onreadystatechange事件来处理生成原子化样式类任务。当页面当中的所有元素都加载完成的时候才开始生成。使用之前定义好的函数扫描整个网页,获取所有的类名。然后将其送入variantGenerator函数,进行生成。至此就完成了一个简单的原子化样式类库,并且是基于运行时的原子化样式类库。当然,实际的原子化样式类库更加复杂,还需要考虑css的兼容问题,以及各种方言,比如需要加-webkit-或者-moz-前缀。当某个样式在低版本css当中不支持,需要将这个样式转换为低版本的等价写法。如此种种。所以说,这只是一个用于套利原理性的样式库,如果读者受到了启发,希望你们能实现一个真正可用的样式库。

原文链接:

如果大家也遇到了这样的情况,或者想要跳槽、涨薪、进阶学习,那么可以留意公重号:码农补给站

标签: #行内样式的优缺点