最近围绕 Svelte 的炒作非常多,而且该项目 在 GitHub 上获得了超过 24,000 个星标。Svelte 可以说是最简单的 JavaScript 框架,由 Rich Harris 开发,他是 Rollup 的开发人员。Svelte 有很多值得喜欢的地方(性能、内置状态管理、编写适当的标记而不是 JSX),但对我来说,最大的吸引力是它对 CSS 的方法。
单文件组件
React 对样式的定义方式没有意见
—React 文档
一个没有内置方法为组件添加样式的 UI 框架是不完整的。
—Rich Harris,Svelte 的创造者
在 Svelte 中,您可以像在典型项目中一样在样式表中编写 CSS。您也可以 使用 CSS-in-JS 解决方案,例如 styled-components 和 Emotion,如果您愿意。将代码划分为组件而不是按文件类型进行划分变得越来越普遍。例如,React 允许将组件的标记和 JavaScript 放在一起。在 Svelte 中,这又向前迈进了一步:组件的 Javascript、标记和样式都可以放在一个 `.svelte` 文件中。如果您曾经在 Vue 中使用过单文件组件,那么 Svelte 看起来会很熟悉。
// button.svelte
<style>
button {
border-radius: 0;
background-color: aqua;
}
</style>
<button>
<slot/>
</button>
样式默认情况下是作用域的
默认情况下,在 Svelte 文件中定义的样式是作用域的。与 CSS-in-JS 库或 CSS 模块类似,Svelte 在编译时生成唯一的类名,以确保一个元素的样式永远不会与另一个元素的样式冲突。
这意味着您可以在 Svelte 组件文件中使用简单的元素选择器(如 div
和 button
),而无需处理类名。如果我们回到前面示例中的按钮样式,我们知道 <button>
的规则集将只应用于我们的 <Button>
组件,而不会应用于页面中的任何其他 HTML 按钮元素。如果您在一个组件中有多个按钮,并且希望以不同的方式对其进行样式设置,您仍然需要使用类。类也将被 Svelte 作用域。
Svelte 生成的类看起来像是乱码,因为它们是根据组件样式的哈希值生成的(例如 svelte-433xyz
)。这比 像 BEM 这样的命名约定容易得多。不过,不得不承认,在 DevTools 中查看样式的体验稍微差一点,因为类名缺乏意义。

这不是非此即彼的情况。您可以将 Svelte 的作用域样式与常规样式表一起使用。我个人在 .svelte
文件中编写组件特定的样式,但会使用样式表中定义的实用程序类。对于要在整个应用程序中使用的全局样式——CSS 自定义属性、可重复使用的 CSS 动画、实用程序类、任何“重置”样式或像 Bootstrap 这样的 CSS 框架——我建议将它们放在与 HTML 文档头部链接的样式表中。
它让我们创建全局样式
正如我们刚刚看到的,您可以使用常规样式表来定义全局样式。如果您需要从 Svelte 组件中定义任何全局样式,也可以使用 :global
来实现。这本质上是在需要时选择退出作用域的一种方法。
例如,模态组件可能希望切换一个类来对 body 元素进行样式设置
<style>
:global(.noscroll) {
overflow: hidden;
}
</style>
未使用的样式会被标记
Svelte 的另一个好处是,它会在编译期间提醒您任何未使用的样式。换句话说,它会搜索定义但从未在标记中使用的样式。
条件类简明扼要,毫不费力
如果 JavaScript 变量名和类名相同,语法就会非常简明。在这个示例中,我正在为全宽按钮和幽灵按钮创建修饰符属性。
<script>
export let big = false;
export let ghost = false;
</script>
<style>
.big {
font-size: 20px;
display: block;
width: 100%;
}
.ghost {
background-color: transparent;
border: solid currentColor 2px;
}
</style>
<button class:big class:ghost>
<slot/>
</button>
当使用 ghost
属性时,元素将应用一个 ghost
类,当使用 big
属性时,元素将应用一个 big
类。
<script>
import Button from './Button.svelte';
</script>
<Button big ghost>Click Me</Button>
Svelte 不要求类名和属性名相同。
<script>
export let primary = false;
export let secondary = false;
</script>
<button
class:c-btn--primary={primary}
class:c-btn--secondary={secondary}
class="c-btn">
<slot></slot>
</button>
上面的按钮组件将始终具有 c-btn
类,但只有在传递相关属性时才会包含修饰符类,如下所示
<Button primary>Click Me</Button>
这将生成以下标记
<button class="c-btn c-btn--primary">Click Me</button>
可以使用单个属性将任意数量的类传递给组件
<script>
let class_name = '';
export { class_name as class };
</script>
<button class="c-btn {class_name}">
<slot />
</button>
然后,可以使用与使用 HTML 标记相同的方式使用类
<Button class="mt40">Click Me</Button>
从 BEM 到 Svelte
让我们看看与标准 CSS 命名约定相比,Svelte 使编写样式变得容易多少。这是一个使用 BEM 编写的简单组件。
.c-card {
border-radius: 3px;
border: solid 2px;
}
.c-card__title {
text-transform: uppercase;
}
.c-card__text {
color: gray;
}
.c-card--featured {
border-color: gold;
}
使用 BEM,类会变得又长又难看。在 Svelte 中,事情变得简单多了。
<style>
div {
border-radius: 3px;
border: solid 2px;
}
h2 {
text-transform: uppercase;
}
p {
color: gray;
}
.featured {
border-color: gold;
}
</style>
<div class:featured>
<h2>{title}</h2>
<p>
<slot />
</p>
</div>
它与预处理器配合得很好
在使用 Svelte 时,CSS 预处理器感觉不那么必要了,但它们可以通过使用名为 Svelte Preprocess 的包完美地协同工作。支持 Less、Stylus 和 PostCSS,但这里我们将介绍 Sass。我们首先需要安装一些依赖项
npm install -D svelte-preprocess node-sass
然后我们需要在文件的顶部在 rollup.config.js
中导入 autoPreprocess。
import autoPreprocess from 'svelte-preprocess';
接下来,让我们找到 plugins 数组,并将 preprocess: autoPreprocess()
添加到 Svelte 中
export default {
plugins: [
svelte({
preprocess: autoPreprocess(),
...other stuff
然后,我们只需要在组件文件中工作时指定我们使用的是 Sass,使用 type="text/scss"
或 lang="scss"
来设置样式标签。
<style type="text/scss">
$pink: rgb(200, 0, 220);
p {
color: black;
span {
color: $pink;
}
}
</style>
无需运行时的动态值
我们已经看到,Svelte 附带了大多数 CSS-in-JS 的优势——但没有外部依赖!但是,第三方库可以做到 Svelte 做不到的一件事:在 CSS 中使用 JavaScript 变量。
以下代码无效,将无法工作:
<script>
export let cols = 4;
</script>
<style>
ul {
display: grid;
width: 100%;
grid-column-gap: 16px;
grid-row-gap: 16px;
grid-template-columns: repeat({cols}, 1fr);
}
</style>
<ul>
<slot />
</ul>
但是,我们可以通过使用 CSS 变量来实现类似的功能。
<script>
export let cols = 4;
</script>
<style>
ul {
display: grid;
width: 100%;
grid-column-gap: 16px;
grid-row-gap: 16px;
grid-template-columns: repeat(var(--columns), 1fr);
}
</style>
<ul style="--columns:{cols}">
<slot />
</ul>
多年来,我以各种不同的方式编写 CSS:Sass、Shadow DOM、CSS-in-JS、BEM、原子 CSS 和 PostCSS。Svelte 提供了最直观、易于使用和友好的样式 API。如果您想阅读更多关于此主题的内容,请查看 Rich Harris 撰写的标题恰如其分的文章 The Zen of Just Writing CSS。
很高兴在 CSS Tricks 上看到一篇关于 Svelte 的文章!您对 BEM 到 Svelte 的解释说明了一切。为什么我们多年来一直使用 BEM,而编译器却可以为我们做到这一点呢?
虽然可能不应该提倡直接在基础元素上写样式代码。即使在作用域代码中,当扩展代码时也可能很容易导致问题。在我看来,一个更好的主意是使用像 Bootstrap 中那样的简单类名。在 Svelte 中,由于缺乏命名空间,你不会遇到 Bootstrap 的 CSS 冲突问题。
一直在尝试使用 Svelte CSS 和基于上下文的主题,利用 CSS 属性(CSS 变量)。这非常强大。应该比通常使用 Styled Components 和 Emotion 的方法要高效得多。特别是使用新的 CSS.registerProperty(),它允许 CSS 变量不继承,使其更有效率,因为它们只影响一个元素。
https://codesandbox.io/s/svelte-theme-components-crt54?fontsize=14&module=%2FButton.svelte
不错的文章!你用过外部 sass 文件吗?我在尝试导入单个全局 global.scss 文件时遇到了麻烦,即使在 style 标签中预处理 sass 是可行的。
干杯!
在
App.svelte
中这就是我今天使用的,但我不能使用 App 的漂亮组件样式 :P 它还不错,但如果有一个更好的解决方案会更好。
所以,你不能将样式从组件中分离出来吗?因为这篇文章的第一部分有点暗示你可以。我不喜欢单文件组件。我喜欢重用我的 CSS,而且我很喜欢 SASS。而且将所有东西(除了厨房水槽)堆成一堆绝对不是“更进一步”。除非它是一步踏入深渊。
你可以将它们分开。
我不认为“未使用的样式会被标记出来”。
我认为它们实际上是默认情况下被删除的 https://github.com/sveltejs/svelte/issues/697,但我不确定如何实际指示 Svelte 不要删除特定样式?
我觉得这很有趣,因为这就是你喜欢 Svelte 的原因,而这正是让我最终放弃 Svelte 的原因(还有使用它的调试很糟糕)。
是的,你提到的所有内容都很好 - 但你会注意到几乎所有内容都与从组件内部进行样式设置或额外的样板代码和冗长代码有关,以允许你从组件外部进行样式设置。虽然你通常需要一些内部 CSS,但只从组件内部进行样式设置并不是现实世界的工作方式。你开始一个新的 web 项目时做的第一件事是什么?在里面扔一个 reset.css 文件。
我想要一个无头 UI 组件系统,这对我的 Svelte 来说简直是噩梦。他们故意让它非常难以以正常自然的方式将样式应用到组件标签本身,而无需大量的样板代码。
你提出的将类传递到组件中的类解决方案对我来说不可靠。Svelte 开始随机覆盖我传入的类,因为它在幕后分配了它的作用域类。我找不到一种方法可以在生产环境中向组件添加一个可靠的类。无法向元素添加类非常有限。
你可以使用 :global 来为元素设置样式,但它是真正的全局样式,这意味着它会泄漏到页面上的所有内容,而不仅仅是你在使用的文件中的元素和组件。因此,你必须将所有内容包装在一个 div 中,并在代码中的所有地方将其范围限定到该 div。
也许最糟糕的是,在 Svelte 中,你使用的组件标签实际上并不代表文档中的真实元素,类似于 html 中的模板标签。对于模板标签来说这是有道理的,因为你可以很容易地定位里面的元素,但对于封闭的组件来说,你不能很容易地定位里面的东西,这就没有道理了。这两个想法在我看来是截然相反的。
这导致了各种问题,我不得不以不自然的方式做一些事情。
我无法在 CSS 中定位元素的标签名称 - 在同一个文档中(除非将其包装在另一个 :global 选择器中)。
我不能像给组件添加一个 id 并用它定位组件插槽中的项目那样做。我必须要么在组件周围再包装一个不必要的 div 并用它对插槽项目进行范围限定,要么 a.) 在组件中添加一个名为 id 的自定义属性,b.) 导出它,c.) 为我的组件分配一个 id,d.) 在我的 css 中,即使我正在定位具有 id 的标签内部的子元素,在这个文档中也必须将其包装在一个 :global 选择器中。
我甚至不能定位组件本身在我的页面上,除非在组件中添加额外的样板代码或将其包装在另一个不必要的 div 中 - 并且在这两种情况下都必须使用另一个 :global 选择器。
结果是,我发现自己将所有组件都包装在额外的 div 中,这只是丑陋、混乱、膨胀了我的 html。他们也意识到了这一点,因为他们不仅推荐你这样做作为解决方案,而且他们在幕后使用语法糖功能(如 css 变量属性)本身就做了这件事。这会导致更多的问题,因为它们会将你无法用 CSS 定位的不可见 div 插入到你的 html 中。
我发现访问组件内部的任何东西以对其进行样式设置或以编程方式更改它,只是我不得不设置的额外样板代码。我最终意识到,使用 Svelte 而不是节省时间,我反而增加了时间。
话虽如此,我肯定没有学习所有做这件事的技巧或方法,可能还有更好的方法,但对我来说,Svelte 提供的不是直观、易于使用和友好的样式 API。