原子化 CSS 真能减少体积么?

news2024/12/22 20:45:31

前言

最近看到这样一篇文章:《要喷也得先做做功课吧?驳Tailwind不好论》

个人觉得说的还是有一定道理的,就是该作者的语气态度可能稍微冲了点:

SCR-20231010-koky.png

不过他说的确实有道理,如果这种原子化工具真的如评论区里那帮人说的那么不堪的话,又怎么会达到百万级的周下载量?而且还呈现出一路增长的态势,当然有人可能会说:哪一路增长了?你看那条曲线,在最右边都掉的不行了,肯定是马上要 G 了。

其实这些周下载量会在某几周呈现出极速下降的趋势一般都不是因为这玩意要没落了,哪有没落的这么快的,一两周用户量就少一半?出现这种情况的原因是因为欧美那边放假了,比方说复活节、圣诞节什么的,大家都出去玩了没人工作了所以才呈现出大幅下降的趋势。当然也不仅仅是欧美,某东方神秘大国放长假时也会呈现出类似效果。这个作者截图的时候可能刚好是欧美有什么假期,不信的话我们现在再来截一遍图看看:

卧槽!他截的是 tailwindcss 的下载量么?怎么我截的时候是五百多万的下载量?差的也太多了点,不过五百多万的下载量更证明了这个工具有多受欢迎,为了有个对比我们再来截一下 Vue 的数据:

可以看到 Vue 的下载量也不过三百来万,所以你是信评论区下面的那些小卡了米说这玩意有多垃圾多不好维护?还是信全球开发者闭着眼投票出来的用户数据?

体验

作为一名已经使用了多年的用户,我可以先跟大家分享一下自己的体验,以及用了这么久的优缺点,是否像评论区说的那么不好用,以及又是否像那些粉丝说的那么好。

首先说一下当初为什么要选择 Tailwind CSS,原因其实很简单:求新!

当时刚来这家公司不久,可能也就三四个月?但我作为一个新人却接到了一个重任:低代码平台!这个低代码是给公司运营同事用的,他们经常会出一些活动页面,这些页面技术含量并不高,交互也比较简单,主要是大量的图片以及一些表格表单等。但架不住页面多啊,总让我们做这些页面非常浪费人力,所以领导就想做一个低代码平台来分担我们的压力,他们运营想要什么页面自己搭建就好了,而我们只根据他们的需求来定制一个专属的低代码平台。

虽然不知道为什么这个重担会委派给我这个新人,但我还是非常开心的(感谢我阳总)因为这个项目只在公司内部使用,所以可以不用考虑那么些个兼容性什么的,我可以放心大胆的使用最新鲜的技术,终于不用再兼容那些个破浏览器啦!

而且这个项目还给了我很大的自由度,那时候 Vite 2.0 才刚发布不久,都没多少人用。甚至 Vue 3 都还算得上是比较新鲜的技术,至少在市面上用 Vue 3 开发的项目还不是很常见。但我当时想的是既然是一个船新项目那就不要留下什么技术债了,虽然当年我选的技术不是很成熟,但随着时间的推移慢慢的他们都会变成熟的嘛!如果现在就束手束脚的还用什么 vue-cli + vue2,随着时间的推移这个项目又跟公司现在维护的老项目没什么区别了。

当然有人可能会问:你怎么知道 Vite 这些新技术会越来越趋向成熟?万一发展一半像 Snowpack 一样噶了呢?我当然不知道某项技术未来的发展趋势,但当时的我就是有一种迷之自信,就是坚信尤雨溪出品的项目一定会有所发展,结果现在 Vite 发展的确实不错,尤大出品,必属精品。

一开始用的是 Vue3 + TSX,那时候是 3.0,还没有 setup 语法糖,每个变量还需要在底部 return 出去很麻烦,但用了 JSX 就不用挨个 return 了,而且 JSX 的灵活性也能让我很方便的使出一些骚操作。

JSXCSS 的结合不像 .vue 文件那么方便,而且 Vue 也不像 React 似的有那么多 CSS-in-JS 的库,不过好在 Vite 原生支持 CSS Module,于是采纳了 TSX + CSS Module 的方案,感觉也挺好用的。

然而某一天我突然刷到了一篇 Tailwind CSS 的文章,其实以前也刷到过,但对这玩意的印象也就一般。不过今时不同往日,一方面是新项目我想把以前没用过的东西都用一遍(前提是 Star 数很高以及 npm 下载量还可以)另一方面感觉这个技术有点击中我的痛点,一方面是 TSX + CSS Module 确实没有 .vue 文件来的方便,毕竟要写两个文件,那有人可能会说你可以写成这样:

<script lang="tsx">
  ...
</script>

<style scoped>
  ...
</style>

当年真的没有这种语法,这是 3.2 才加上去的,当时只有 3.0 这个版本可以选,而且我也不知道后来他能搞这么多语法糖进去,我还以为一直都会写成这样呢:

<template>
  ...
</template>

<script lang="ts">
export default {
  setup () {
    ...
    return {
      a,
      b,
      c,
      d,
      e,
      f,
      g,
      ...
    }
  }
}
</script>

Tailwind 的出现让我觉得可以不用再写成两个文件了,一个.tsx文件就搞定了。并且另一个痛点就是起名,比方说某个元素可能仅仅只需要往下稍微移一点,只需要设置一个 margin-top 就行,但你还需要给它起个类名,有时我就直接起名 mt,当然这个类名还遭到了批评,让我不要这么写,所以后来我就干脆直接在标签上写个 style

<div style="margin-top: 2px">
  ...
</div>

你说这不就和 Tailwind 有点类似了么?我看评论区好多喷 Tailwind 的都说当样式多了以后可能会写出这样的代码难以维护:

<div class="relative p-3 col-start-1 row-start-1 flex flex-col-reverse rounded-lg bg-gradient-to-t from-black/75 via-black/0 sm:bg-none sm:row-start-2 sm:p-0 lg:row-start-1">
  <h1 class="mt-1 text-lg font-semibold text-white sm:text-slate-900 md:text-2xl dark:sm:text-white">Beach House in Collingwood</h1>
  <p class="text-sm leading-4 font-medium text-white sm:text-slate-500 dark:sm:text-slate-400">Entire house</p>
</div>

说实话写成这样确实有点恶心,看起来也不是很容易,但别忘了 Tailwind 并不会影响你原有的写法,如果真的有比较复杂的样式你就写在 css 文件里或者写在 .vue<style> 标签里就行了啊:

<template>
  <div class="card">
    <h1 class="title">{{ xxx }}</h1>
    <p class="content">{{ xxx }}</p>
  </div>
</template>

<style scoped>
  .card { ... }
  .title { ... }
  .content { ... }
</style>

然后只在简单样式的情况下使用 Tailwind,按照这个原则把他俩结合到一起:

<template>
  <div class="card">
    <h1 class="title w-10 h-5">{{ xxx }}</h1>
    <div class="mt-2 px-4" />
    <p class="content">{{ xxx }}</p>
  </div>
</template>

<style scoped>
  .card { ... }
  .title { ... }
  .content { ... }
</style>

这样不就能达到很完美的互补了么?Tailwind 解决了简单样式不想写类名的困扰,也免除了上面写一个类名,然后滚动个几百行跑到下面定义这个类名,结果却仅仅只是写了个 margin-top!而且也比写 style 简洁,还能获得更小的打包尺寸。

更小的打包尺寸怎么讲?比方说我原来的方案,不想写类名有时就直接写 style

<div style="margin-top: 2px">
  ...
</div>

style 就只能作用在单个元素上,另一个元素也需要 margin-top: 2px,那是不是就相当于写了俩 margin-top?但它俩的样式一模一样,此时类的优势就体现出来了:

<!-- 某组件 -->
<div class="mt-2" />

<!-- 其他组件 -->
<p class="mt-2" />

有人可能会想:这刚能节省几个字符?有道是积少成多,用 Tailwind 的地方多了,重复的样式也不可避免的会变多,那到底能节省多大的尺寸呢?会不会有可能尺寸反而变得更大了呢?这正是本篇文章将要进行的实验,不过在实验之前我们还是先来说说这玩意的缺点。

缺陷

我知道为什么老有人担心用 Tailwind 会写出这样的代码:

<div class="relative p-3 col-start-1 row-start-1 flex flex-col-reverse rounded-lg bg-gradient-to-t from-black/75 via-black/0 sm:bg-none sm:row-start-2 sm:p-0 lg:row-start-1">
  <h1 class="mt-1 text-lg font-semibold text-white sm:text-slate-900 md:text-2xl dark:sm:text-white">Beach House in Collingwood</h1>
  <p class="text-sm leading-4 font-medium text-white sm:text-slate-500 dark:sm:text-slate-400">Entire house</p>
</div>

虽然我知道复杂样式正常写,简单样式再用 Tailwind 的方案:

<template>
  <div class="card">
    <h1 class="title">{{ xxx }}</h1>
    <div class="mt-2 px-4" />
    <p class="content w-10 h-5">{{ xxx }}</p>
  </div>
</template>

<style scoped>
  .card { ... }
  .title { ... }
  .content { ... }
</style>

但刚开始用的时候我的强迫症还是迫使了自己进行二选一,为了像 .vue 文件那样只写一个 .jsx 而不用再多写一个 xxx.module.css,毕竟这个后缀挺长的,每次写都在消耗我的耐心。于是我的强迫症还是让我写出了类似这样的代码(不过没这么夸张):

<div class="relative p-3 col-start-1 row-start-1 flex flex-col-reverse rounded-lg bg-gradient-to-t from-black/75 via-black/0 sm:bg-none sm:row-start-2 sm:p-0 lg:row-start-1">
  <h1 class="mt-1 text-lg font-semibold text-white sm:text-slate-900 md:text-2xl dark:sm:text-white">Beach House in Collingwood</h1>
  <p class="text-sm leading-4 font-medium text-white sm:text-slate-500 dark:sm:text-slate-400">Entire house</p>
</div>

当一行属性太长的时候我们通常都会给它换个行:

<h1
  class="
    mt-1
    text-lg
    font-semibold
    text-white
    sm:text-slate-900
    md:text-2xl
    dark:sm:text-white
   "
>
  Beach House in Collingwood
</h1>

看起来是不是好多了?但假如此时我们需要一个动态类名,在 .vue 文件里我们可以写成这样:

<h1
  :class="{ title: isLoading }"
  class="
    mt-1
    text-lg
    font-semibold
    text-white
    sm:text-slate-900
    md:text-2xl
    dark:sm:text-white
   "
>
  Beach House in Collingwood
</h1>

也就是说我们可以写把同一个属性的静态内容和动态内容分开写,这在 .vue 文件中是完全没问题的,但到了 .jsx 这边:

它就一直给你飘红,注意我这里用的是 .jsx,还不是 .tsx.tsx 会让你连编译都通不过。所以怎么办?那就只能这么办咯:

<a class={[{ title: isLoading }, 'mt-1 text-lg font-semibold text-white sm:text-slate-900 md:text-2xl dark:sm:text-white']} />

不换行还好(其实也挺乱的)但只要一换行:

肯定有人会说:那你把单引号改成反引号不就得了:

这样确实没问题了,但问题在于该项目用了保存文件时自动运行 prettiereslint,换行啥的根本不用自己操心,随便写,写完 command + s 一按,文件立马给你格式化:

这个功能特别方便,我很喜欢用,但用了反引号,换行就得靠你自己手动对齐了:

这真的让人挺不爽的,而且写成这样的话提示也都没了,静态 class 里会显示你写的颜色(需提前安装插件):

但写成动态 + 字符串形式的话这些效果通通会消失:

当然这也不是什么太大不了的事,看不见颜色也没那么大的影响。但在很多情况下是先写静态样式,然后才发现这块忘考虑 Loading 时的样式,再给加上动态代码,在 .vue 文件里写起来就很方便:

但在 .jsx 这边可就遭老罪咯:

除非你一开始就想好这个标签上是否有动态类,不然的话改起来你就说麻不麻烦吧?当然这也不应该算是 Tailwind 的缺点,应该算是 JSX 的缺点。但这个缺点被我随后刷到的 Windi CSS 解决了,当时刚用 Tailwind 不久,就刷到了 Windi CSS,那时候的前端真是日新月异,我才刚用熟,就又出替代品了:

当时 Tailwind 还在 2.x,没有 JIT 模式,速度方面被 Windi 吊打。而且 Windi 有个属性模式很实用,原本 Tailwind 都是写在 class 属性内的,但实际上有很多重复的前缀,比如:

这个 class 已经写的稍微有点长了,但换成 Windi 后:

<button
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
/>

一下子就短了好多,不再像之前那样一眼望去一大串类名了,而且也很好的解决了换行问题,毕竟你用的属性都被分了组,一组里的内容反而是有限的,比方说你想给个边距,那就是 m 属性,边距刚能有几个选项啊,这么多顶头了:

<button
  m="t-1 b-2 l-3 r-4 x-5 y-6"
/>

通常情况下根本就写不了这么多,并且它还没有占用 class 属性,假如突然想加一个类名,就不用像以前似的那么麻烦了:

并且 Windi 最牛逼的一个功能就是自定义语法,这正是我非常需要的一个特性,比如说我需要 margin-top: 16.5px,但 Tailwind 根本就没定义那么细,它的预置内容里没有 16.5px,所以我只能把它写进 xxx.module.css 里。但 Windi 说了:预置里没有 16.5px 是吧?没关系!你只需要写成这样:

<button m="t-16.5px" />

Tailwind CSS 现在倒是支持这种语法,不过需要加个中括号有些略显麻烦:

<button m="t-[16.5px]" />

但当年的 Tailwind 是没有这些个花里胡哨的,在我眼里 Windi 简直就是 Tailwind Pro Max Ultra!于是乎我二话不说就把 Tailwind 换成了 Windi,然后把之前 class 里的那一坨挨个重写成属性模式,文件有点多,花了不少时间。而且由于用的是 .tsx,这些没定义的属性会报错:

而且也容易让人分不清你写的到底是 Windi 的属性还是真的要传给组件的属性,Windi 早就替你想到了这一点,可以给属性加前缀,我加的前缀是:

import { defineConfig } from 'vite-plugin-windicss';

export default defineConfig({
  attributify: {
    prefix: 'css-:',
    separator: '__',
  },
});

为什么要用 css-: 这个前缀,一是带冒号会有一个不同的颜色,容易区分于普通属性:

二是我意外发现的神奇事件,只要属性是字母 + - + :TS 就不会再报错了:

等到周五写周报的时候我傻眼了,我这周大部分时间都用在重构上了,没怎么开发新功能,咋写啊?

本周工作:

  • Tailwind CSSWindi CSS

结果果然不出我所料,我被叫去小黑屋谈话了:

你应该以项目功能为主,不要老想着天天重构项目。前端是个变化非常迅速的行业,你要是追新的话你永远也追不完,它一天就能给你产出来一堆新技术来,难道每次出新技术你都要重构一遍么?

后来果不其然,Windi CSSG 了。那时候我听说 antfu 进入了 windi 团队,然后准备出一个 UnoCSS,记得原话说的好像是将会用 UnoCSS 来当作 Windi 的引擎,你可以理解为 UnoWindi 团队的一次激进实验。所以我一直都很期待,但后来慢慢的发现 Windi 的更新频率越来越低,不知怎么回事,结果一查说是 Windi 的作者在推特还是哪跟人发生了一次激烈争吵,心灰意冷了。当然这并不是官方说法,官方说法是这样的(机译):

我刚被约谈没多久,你就告诉我 WindiG 了?我现在换成 Uno 是不是过段日子又特么出新品了?这玩意出的怎么比手机都快?为了防止再次挨骂,这次我就没换,因为 Windi 也确实用的好好的没什么毛病。不过这个 Uno 不是我每天都在用的洗面奶么:

于是 Windi 就一直用到现在,不过写法跟 Uno 应该也没啥太大区别,就当是 Windi 的继任者好了。扯远了,咱们继续来说缺点,Tailwind 还有一个缺点,不过缺点这个词用的也不是特别准确,因为它既是缺点又是优点,具体取决于你所开发的项目类型。看到这有人可能会说:你跟我在这玩薛定谔的猫呐:

这个缺点就是单位,当你写了 mt-1 这个类时,你可能会以为是 margin-top: 1px,然而实际上却是 margin-top: 0.25rem,那 0.25rem 又是多少呢?按 1rem = 16px 来换算,它应该是 16px * 0.25,这个数乍一看不是那么好算是吧?我们可以把 0.25 看作四分之一,16px * 1/4 就相当于 16px 除以 4

难道每次写 Tailwind 的时候都要心算一遍么?太累了吧?这玩意用上个两三年之后是不是就成心算高手了?但你要是换个角度来看的话这个设计又成为了一个优点,就是当你写一个不是那么精细的响应式 H5 的项目,这玩意还蛮好用的,因为 1rem 具体等于多少 px 是可以根据屏幕尺寸的不同来给根元素一个不同的 font-size 从而进行动态变化的。不过对于我们来讲都是有设计图的,少 1px 测试都会提 bug,所以我更希望的是 mt-1 就代表 margin-top: 1px,既好记又精细。

于是 UnoCSS 说:这需求我熟啊!我可以很方便的进行预设:

并且也提供了各种常用预设:

总之不仅性能好,而且还非常灵活。

优势

不能光说缺陷不说优势是不?那我就谈谈这两年使用上让我觉得非常舒心的地方,就是有时候改版需要改点样式,但改版也不至于说跟原来的样式一点都不一样,大部分都是一样的,只有少部分变化。假如按钮往下移一点、banner 变宽点这种小改,然后进入到项目中,找到按钮,发现这款按钮有个类名叫:

<button class="casino-header-user-avatar-btn">
  ...
</button>

大部分人都会直接搜这个类名,搜到以后直接加一个 margin-top 对吧?但一搜却发现根本搜不到这个类名,因为这种类名一般都是通过 CSS 预处理器定义而来的,比方说这个项目叫 casino,然后写 header 的样式:

.casino {
  ... /* 此处省略 N 行干扰代码 */
  &-header {
    ... /* 此处省略 N 行干扰代码 */
    &-user {
      ... /* 此处省略 N 行干扰代码 */
      &-avatar {
        ... /* 此处省略 N 行干扰代码 */
        &-btn {
          ...
        }
        ... /* 此处省略 N 行干扰代码 */
      }
      ... /* 此处省略 N 行干扰代码 */
    }
    ... /* 此处省略 N 行干扰代码 */
  }
  ... /* 此处省略 N 行干扰代码 */
}

按理说一个组件最好不要超过 300 行,但有的复杂组件经常超过 500 行,甚至上千行的我都遇到过(应该也写过),当搜不到一个类名时那就要疯狂滚动鼠标滚轮,滚到冒烟自己去下面一行行找,找到哪个才是控制 button 的类,再去添加样式。这还不是最痛苦的,最痛苦的是这个按钮不止一个类名,或者有人用了这种写法:

.xx {
  ... /* 此处省略 N 行干扰代码 */
  .xxx {
    ... /* 此处省略 N 行干扰代码 */
    button {
      margin-top: 3px;
    }
  }
  ... /* 此处省略 N 行干扰代码 */
}

当我们好不容易找到了对应的类名并且加入了 margin-top 却发现不生效时,我们就只好打开控制台找到元素看看是什么样式把我们的 margin 给覆盖了,找了以后又去一顿搜,总之挺麻烦的。后来干脆就不这么干了,下次再有这种就直接在标签里写一个 style="margin-top: 6px",写着写着突然就意识到:这不跟写 Tailwind 差不多么?

当然肯定会有人说哪差不多了,style 有更高的权重,能覆盖你在各种类下产生相互影响的样式,你要是写 Tailwind 能覆盖么?但假如一开始这个项目就是用 Tailwind 写的,我们可以在类名里看到:

<button class="w-10 h-4 bg-yellow mt-3">
  ...
</button>

我们是不是就可以省去滚动几百行代码去找类名的这么一个步骤,直接在标签上改成 mt-6 就行了?如果用的是 UnoCSS 属性模式的话那就更方便了,直接找到 m 这个属性改里面的数字就行了:

<button
  w="10"
  h="4"
  bg="yellow"
  m="t-6"
>
  ...
</button>

看到这有人可能会说,你看这个属性模式也没节省什么代码,class 里面有四个类:class="w-10 h-4 bg-yellow mt-3",换成属性模式后不还是四个属性么?

别忘了 UnoCSS 是可以非常方便的自定义插件的,比方说最常用的宽高,它俩被分为了两个属性对吧?我们可以自定义一个 size 属性表示宽高:

<button
  size="w-10 h-4"
  bg="yellow"
  m="t-6"
>
  ...
</button>

然后还可以把与颜色相关的分为一组,比方说前景色、背景色等:

<button
  size="w-10 h-4 border-box"
  color="yellow bg-blue"
  m="t-6"
>
  ...
</button>

再把盒模型分为一组,包括 paddingmarginborderbox-sizing 等:

<button
  size="w-10 h-4"
  color="yellow bg-blue"
  box="mt-6 pb-4 border-box"
>
  ...
</button>

这样可不仅仅只是代码行数减少了,更是可以通过分组一眼望去就能大概看出是个什么样式。

还有就是简单样式时也很方便,以往常见的写法都是上面定义一个类名,然后滚轮一顿滚,再在下面写样式,然后滚轮再滚回去,滚回去的过程中就找不到是哪行了,反正当代码多起来的时候我就是这种感受。而用了 Tailwind 之后想写什么样式我就直接写在标签里了,根本不用滚!

尤其是在 jsx 文件中,jsx 本来无法写样式,当然我指的是 .vue 文件中 <style> 里的那种样式,不是这种:

const btnStyle = { color: 'pirple' }

<button style={btnStyle}>...</button>

当然也不是 CSS-in-JS 那种:

const Flex = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
`

<Flex>...</Flex>

但用了原子化 CSS 就让 jsx 也能很方便的拥有样式了,虽说复杂样式比较捉襟见肘,还得借助其它方案,但简单样式时真的还蛮好用的。

实验

那么接下来我们就来做一个实验,当然这个实验并不是为了对比 TailwindUno 谁更快,肯定是 Uno 快,官方宣传是 5 倍快。

这个实验主要是想搞清楚这些个原子化 CSS 究竟会不会缩小打包后的体积,CSS 的体积必然会有所减少,但与此同时 HTML 体积又会有所增大,此消彼长的情况下究竟谁能够更胜一筹?

实验如下:

  • Vue 3 只用 Tailwind 写一个 TodoList
  • Vue 3 只用 UnoCSS 的属性模式写一个 TodoList
  • Vue 3 复杂样式用普通写法、简单样式用 Tailwind 写一个 TodoList
  • Vue 3 复杂样式用普通写法、简单样式用 UnoCSS 的属性模式写一个 TodoList
  • Vue 3 不用任何原子化 CSS 写一个 TodoList
  • Vue 3 不用任何原子化 CSS(但用内置的 scoped)写一个 TodoList
  • Vue 3 只用 CSS Module 写一个 TodoList

然后分别将其打包,对比一下各自的体积大小:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个 TodoList 写这么花?别提了,一开始做了一版中规中矩的比较简陋简洁的 TodoList,哪成想打包出来的产物差异很小,几乎可以忽略不计,没有任何参考价值。仔细想想也是,毕竟一个 TodoList 刚能有几行代码?原子化 CSS 肯定是样式文件越多,重复性才越高,才越有实验的价值。于是我才把样式尽可能往复杂了写,写的时候还发现 Vue 唯一公认的拖拽库断代了,具体可以看一下这篇:

《别再卷组件库了,Vue 拖拽库都断代了!》

但没想到即使我把 TodoList 写的都这么花了,打包出来的产物依然还是没有什么太大区别。后来分析了一下,一方面是无论 TodoList 写的有多花,它的体量还是太小了。另一方面则是这款 TodoList 的复杂样式和我们日常开发项目的那种复杂样式并不属于同一种复杂,咱们日常开发时很少会出现这么炫酷的动画和交互,这些动画基本上也不太容易重复。但那些普通元素什么的很多,每个元素单拎出来都不复杂,但组合到一起就麻烦了,又要考虑定位、又要考虑相互之间的作用什么的,同时也会出现更多的雷同 CSS 代码。所以最好的方式是将已有的项目分别用 TailwindUno、单文件组件以及 CSS Module 等方式重构几遍,然后再对比打包后的差异。

思来想去,一个至少达到中型规模的项目才能够达到测试的目的(真没法用巨石应用来测试,巨石应用需要好多人合作开发数年才行,有多庞大就不说了,肯定也是屎山项目,太费劲)所以我打算把自己熟悉的项目重新改造,最终此消彼长依然没有什么太大差距:

所以说如果你用原子化 CSS 工具是为了减少打包体积的话,那你可以洗洗睡了,但如果你是为了写起来更爽的话,那确实还值得一试。

往期精彩文章

  • 《产品经理:能不能把 Vue 的中文输入法 bug 解决了?》
  • 《产品经理:能不能让这串数字滚动起来?》
  • 《尤雨溪海外直播:亲手带你写个简易版的Vue!》
  • 《波浪动画很常见,但这个波浪组件绝对不常见》
  • 《UI:你们有没有什么花哨点的组件库给我参考一下?》
  • 《Vue 超好玩的新特性:在 CSS 中引入 JS 变量》
  • 《Vue 超好玩的新特性:DOM 传送门》
  • 《不依赖任何库打造属于自己的可视化数据地图》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1182303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

苹果手机的警示!电子产品无线升级=救命的机会

大家日常使用手机都知道&#xff0c;手机系统和软件三天两头就收到更新提醒。 只要用户手机联网&#xff0c;就可以想更新就更新&#xff0c;觉得原本使用顺手也可以不更新。 可各大厂商的初衷是好的&#xff0c;希望改善系统的一些bug问题&#xff0c;也会给我们带来一些全新功…

强化学习中的基本术语

0.引言 本篇文章主要介绍强化学习中最基本的术语&#xff08;不包含具体算法&#xff09;&#xff0c;主要提供给刚入门强化学习的朋友们&#xff0c;让大家快速掌握一些基本术语&#xff0c;之后对看强化学习算法内容有着更好地理解。 1.基本术语 1.1.state 中文称为“状态…

基于ssm企业人事管理系统

功能如图所示 摘要 基于SSM&#xff08;Spring SpringMVC MyBatis&#xff09;框架的企业人事管理系统是一种高效、可定制化的人力资源管理解决方案。该系统整合了现代企业的人力资源需求&#xff0c;提供了一套功能丰富的工具&#xff0c;用于管理员工信息、薪资、考勤、招聘…

C语言--typedef的使用

前言 在C语言中使用结构体时必须加上struct这个关键字,那有没有办法省略这个呢?要想达到这个目的就 需要用到关键字typedef,顾名思义”类型定义”。 typedef 数据类型 新的别名; 它是用来操作数据类型。其主要作用有两个: 1.给一个较长较复杂的类型取一个简单的别名。 2.给类…

python 之字典的相关知识

文章目录 字典的基本特点&#xff1a;1. 定义2. 键唯一性3. 可变性4. 键的类型 基本操作&#xff1a;字典的创建1. 花括号 {}2. dict() 构造函数3. 键值对的 dict() 构造函数使用 zip() 函数创建字典&#xff1a;注意事项访问字典中的值修改和添加键值对删除键值对 字典方法&am…

平衡树相关笔记

引入 二叉查找树 二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;又名二叉搜索树。满足以下性质&#xff1a; 对于非空的左子树&#xff0c;左子树点权值小于根节点。对于非空的右子树&#xff0c;左子树点权值大于根节点。二叉查找树的左右子树均是二叉…

Android修行手册 - 模板匹配函数matchTemplate详解,从N张图片中找到是否包含五星

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

麒麟v10 安装jenkins

1.想安装哪个版本&#xff1f; https://pkg.jenkins.io/redhat-stable/ 我们查看我们想要哪个版本&#xff1a; 4年前安装的是 Jenkins2.279 版本 现在在docker 上安装的是Version 2.425 版本 2.碰到到的问题 1.安装老版本的Jenkins&#xff0c;会出现安装的插件不兼容&…

win版redis详细安装教程

一、下载 github下载地址 https://github.com/MicrosoftArchive/redis/releases 可选择&#xff1a;下载msi包或zip压缩包 这里我选择的是zip压缩包&#xff0c;直接通过cmd命令窗口操作即可。 二、安装步骤 1、解压Redis压缩包 选中压缩包&#xff0c;右键选择解压&#…

WordPress 主题QUX DUX加强版

模板简介&#xff1a; QUX主题是由轻语博客在大前端DU主题的基础上集成了Ucenter&Market插件后修改加强版&#xff0c;在原有主题的基础上迎合市场增加了很多的功能。主题已解密去授权。 更新内容&#xff1a; 新增关闭注册验证码&#xff08;主题设置–会员中心&#xff…

【黑马程序员】SpringCloud——微服务

文章目录 前言一、服务架构演变1. 单体架构2. 分布式架构2.1 服务治理 3. 微服务3.1 微服务结构3.2 微服务技术对比3.3 企业需求 二、SpringCloud兼容性 三、服务拆分及远程调用1. 服务拆分1.1 服务拆分注意事项1.2 导入服务拆分 Demo 2. 远程调用2.1 根据订单 id 查询订单功能…

2023年10月CSDN客服月报|解决9个重大问题、12个次要问题、12个一般问题,处理5个用户需求

听用户心声&#xff0c;解用户之需。hello&#xff0c;大家好&#xff0c;这里是《CSDN客诉报告》第25期&#xff0c;接下来就请大家一同回顾我们10月份解决的bug&#xff5e; 一、重大问题 1、全站无法访问 反馈量&#xff1a;90问题原因&#xff1a;DDOS攻击具体表现&…

day3 ARM

【昨日作业】 .text .global start _start: mov r0,#0 存放sum mov r1,#1 存放相加的数值 loop: cmp r1,#100 bhi wh add r0,r0,r1 add r1,r1,#1 b loop wh: b wh .end 【内存读写指令】 通过内存读写指令可以实现向内存中写入指定数据或者读取指定内存地址的数据 c语言内存…

【Python3】【力扣题】231. 2 的幂

【力扣题】题目描述&#xff1a; 此题&#xff1a;n为整数&#xff08;32位有符号整数&#xff09;&#xff0c;x为整数。 2 的幂次方都大于0。若幂为负数&#xff0c;则0<n<1&#xff0c;n不为整数。 因此&#xff0c;n为正整数&#xff0c;x为0和正整数。 若二进制表示…

学为贵雅思写作备考

准确通顺&#xff0c;言之有物 两次读不懂&#xff0c;6分以下&#xff0c; 6分没有印象&#xff0c;味同嚼蜡&#xff0c;但是没错&#xff08;书面语过关&#xff09; 英语比较过关 8-9分&#xff0c;很有见地 6-7单个的句子读得懂&#xff0c;前后是贯通的、逻辑是通顺…

决策式AI与生成式AI

人工智能中深度学习&#xff0c;是一种受人脑的生物神经网络机制启发&#xff0c;并模仿人脑来解释、处理数据的机器学习技术&#xff0c;它能自动对数据进行特征提取、识别、决策和生成。它可以从不同的维度进行划分&#xff0c;如果按模型的特点来划分可分为决策式AI和生成式…

open clip论文阅读摘要

看下open clip论文 Learning Transferable Visual Models From Natural Language Supervision These results suggest that the aggregate supervision accessible to modern pre-training methods within web-scale collections of text surpasses that of high-quality crowd…

我的AIGC部署实践02

我的AIGC部署实践02 上次的教程我们实现了在趋势云平台上创建项目并运行对应的代码。虽然有比较好的结果&#xff0c;不过很多时候我们往往需要根据自己的需要更改代码&#xff0c;那么代码运行及部署的流程是怎么样的呢&#xff1f;让我们继续往下看吧。 1.学习准备 官方代码…

测试工程师面试攻略:教你如何描述项目经验

俗话说的好&#xff0c;知己知彼百战百胜。项目经验说的好不好&#xff0c;直接决定了你值多少钱。满满的干货&#xff0c;先三连再耐心看完。 去面试之前&#xff0c;建议大家把简历写得从业经历和项目经验熟练的背下来&#xff0c;能说出项目经验里的详细的测试情况最好。 一…

工程(十三)——基于激光视觉的SLAM三维重建

博主创建了一个科研互助群Q&#xff1a;772356582&#xff0c;欢迎大家加入讨论。这是一个科研互助群&#xff0c;主要围绕机器人&#xff0c;无人驾驶&#xff0c;无人机方面的感知定位&#xff0c;决策规划&#xff0c;以及论文发表经验&#xff0c;以方便大家很好很快的科研…