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

前言

最近看到这样一篇文章:《要喷也得先做做功课吧?驳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>

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

<h1class="mt-1text-lgfont-semiboldtext-whitesm:text-slate-900md:text-2xldark:sm:text-white"
>Beach House in Collingwood
</h1>

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

<h1:class="{ title: isLoading }"class="mt-1text-lgfont-semiboldtext-whitesm:text-slate-900md:text-2xldark: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 后:

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

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

<buttonm="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 这个属性改里面的数字就行了:

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

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

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

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

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

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

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

<buttonsize="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.rhkb.cn/news/185683.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

asp.net core mvc之路由

一、默认路由 &#xff08;Startup.cs文件&#xff09; routes.MapRoute(name: "default",template: "{controllerHome}/{actionIndex}/{id?}" ); 默认访问可以匹配到 https://localhost:44302/home/index/1 https://localhost:44302/home/index https:…

idea使用gradle教程 (idea gradle springboot)2024

这里白眉大叔&#xff0c;写一下我工作时候idea怎么使用gradle的实战步骤吧 ----windows 环境----------- 1-本机安装gradle 环境 &#xff08;1&#xff09;下载gradle Gradle需要JDK的支持&#xff0c;安装Gradle之前需要提前安装JDK8及以上版本 https://downloads.gra…

【遮天】叶凡首次高燃时刻,暴打姜峰逼其下跪,故事逐渐燃情

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 深度爆料&#xff0c;《遮天》国漫30集剧情最新内容解析&#xff0c;前面剧情中&#xff0c;叶凡被姜峰如疯狗一般追杀&#xff0c;他像一只被狼群追逐的鹿&#xff0c;在山林中亡命逃窜。身后是姜峰那歇斯底…

el-date-picker精确到分钟

0 效果 1 代码 使用format、value-format属性格式化即可 :clearable“false” // 取消删除图标 注意&#xff1a; format&#xff1a;“yyyy-MM-dd HH:mm” 小时默认是从00:00开始 format&#xff1a;“yyyy-MM-dd hh:mm” 小时默认是从12:00开始

torch.cumprod实现累乘计算

cumprod取自“cumulative product”的缩写&#xff0c;即“累计乘法”。 数学公式为&#xff1a; y i x 1 x 2 x 3 . . . x i y_ix_1\times{x_2}\times{x_3}\times{...}\times{x_i} yi​x1​x2​x3​...xi​ 官方链接&#xff1a;torch.cumprod 用法&#xff1a; impo…

代码随想录训练营Day1:二分查找与移除元素

本专栏内容为&#xff1a;代码随想录训练营学习专栏&#xff0c;用于记录训练营的学习经验分享与总结。 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;二分查找与移除元素 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a…

基于CLIP的图像分类、语义分割和目标检测

OpenAI CLIP模型是一个创造性的突破&#xff1b; 它以与文本相同的方式处理图像。 令人惊讶的是&#xff0c;如果进行大规模训练&#xff0c;效果非常好。 在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 3D…

算法进阶指南图论 道路与航线

其实再次看这题的时候。想法就是和强连通分量有关&#xff0c;我们很容易发现&#xff0c;题目中所说的双向边&#xff0c;就构成了一个强连通分量&#xff0c;而所谓的单向边&#xff0c;则相当于把强连通分量进行缩点&#xff0c;然后整个图成为了一个DAG&#xff0c;众所周知…

go程序获取工作目录及可执行程序存放目录的方法-linux

简介 工作目录 通常就是指用户启动应用程序时&#xff0c;用户当时所在的文件夹的绝对路径。 如&#xff1a;root用户登录到linux系统后&#xff0c;一顿cd&#xff08;change directory&#xff09;后, 到了/tmp文件夹下。此时&#xff0c;用户要启动某个应用程序&#xff0…

Mybatis-Plus同时使用逻辑删除和唯一索引的问题及解决办法

1 问题背景 在开发中&#xff0c;我们经常会有逻辑删除和唯一索引同时使用的情况。但当使用mybatis plus时&#xff0c;如果同时使用逻辑删除和唯一索引&#xff0c;会报数据重复Duplicate entry的问题。 举例来说&#xff0c;有表user&#xff0c;建立唯一索引&#xff08;u…

Qt实现动态桌面小精灵(含源码)

目录 一、设计思路 二、部分源码演示 三、源码地址 🌈write in front🌈 🧸大家好,我是三雷科技.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由三雷科技原创 CSDN首发🐒 如需转载还请通知⚠️ 📝个人主页:三雷科技🧸—CSDN博客 🎁欢…

了解高防服务器的工作原理

在当今互联网时代&#xff0c;网络安全问题日益突出&#xff0c;各种网络攻击层出不穷。为了保护企业的网络安全&#xff0c;高防服务器应运而生。那么&#xff0c;你是否了解高防服务器的工作原理呢?下面就让我们一起来探索一下。 高防服务器是一种能够有效抵御各种网络攻击的…

万界星空科技MES系统软件体系架构及应用

MES系统是数字化车间的核心。MES通过数字化生产过程控制&#xff0c;借助自动化和智能化技术手段&#xff0c;实现车间制造控制智能化、生产过程透明化、制造装备数控化和生产信息集成化。生产管理MES系统主要包括车间管理系统、质量管理系统、资源管理系统及数据采集和分析系统…

C语言编写一个程序采集招聘信息

因为在这里无法详细解释每行代码和步骤。但是&#xff0c;我可以给大家一个使用Python和requests库编写的简单爬虫程序的例子&#xff0c;它可以从网站上获取招聘信息。你可以根据这个例子&#xff0c;将其改写为使用C语言编写的爬虫程序。 import requests# 指定爬虫IP信息 pr…

软件测试|测试方法论—边界值

边界值分析法是一种很实用的黑盒测试用例方法&#xff0c;它具有很强的发现故障的能力。边界值分析法也是作为对等价类划分法的补充&#xff0c;测试用例来自等价类的边界。 这个方法其实是在测试实践当中发现&#xff0c;Bug 往往出现在定义域或值域的边界上&#xff0c;而不…

“锡安主义”贝尔福宣言希伯来抵抗运动犹太启蒙改革运动奋锐党闪米特人雅利安人

目录 “锡安主义” 贝尔福宣言 希伯来抵抗运动 犹太启蒙改革运动 奋锐党 闪米特人 雅利安人 “锡安主义” “锡安主义”是一种政治和民族运动&#xff0c;旨在支持并促进犹太人建立自己的国家并在历史上与宗教上的祖先之地——巴勒斯坦地区建立一个独立的国家。这一运动…

nginx图片资源管理转发

目标: 服务器上面 /home/images 里面作为文件资源管理器 代码: server {listen 80;server_name hello.world.cn;#apple-app和接口的关联文件location ~.*(images/miniapp)*\.(gif|jpg|jpeg|png)$ {root /home/;try_files $uri $uri/ 404;add_header Cache-Control &q…

“第六十四天” 字扩展和位扩展,外部存储器

存储器和CPU的连接; 现在的计算机MAR&#xff0c;MDR通常集成在CPU内部。存储芯片内只需一个普通的寄存器&#xff08;暂存输入&#xff0c;输出数据&#xff09;。 位扩展&#xff0c;字扩展&#xff0c;字位同时扩展&#xff1b; 位扩展&#xff1a; 位扩展的增加的是主存的…

unity【动画】脚本_角色动画控制器 c#

首先创建一个代码文件夹Scripts 从人物角色Player的基类开始 创建IPlayer类 首先我们考虑到如果不挂载MonoBehaviour需要将角色设置成预制体实例化到场景上十分麻烦&#xff0c; 所以我们采用继承MonoBehaviour类的角色基类方法写代码 也就是说这个脚本直接绑定在角色物体…