css-in-js 探讨

15次阅读

共计 5162 个字符,预计需要花费 13 分钟才能阅读完成。

Web 开发是需要掌握多种技术。我们习惯于与多种语言密切合作。而且,随着开发 Web 应用程序变得越来越普遍和差别细微化,我们经常寻找创造性的方法来弥合这些语言之间的差距,从而使我们的开发环境和工作流程更容易,更高效。
最常见的示例通常是使用模板语言时。例如,可以使用一种语言来生成更详细的语言(通常是 HTML)的代码。这是前端框架的关键作用之一 - 操作 HTML。这个领域最出名的就是 JSX,因为它不是真正的模板语言; 它是 JavaScript 的语法扩展,它使得使用 HTML 非常简洁。
Web 应用程序经历了许多状态组合,单独管理状态通常很有挑战性。这就是为什么 CSS 有时会被淘汰的原因 – 即使通过不同的状态和媒体查询管理样式同样重要且同样具有挑战性。在这个由两部分组成的系列中,我想将 CSS 放在聚光灯下,并探索弥合它与 JavaScript 之间的差距。在本系列中,我将假设您正在使用像 webpack 这样的模块解析器。因此,我将在我的示例中使用 React,但相同或类似的原则适用于其他 JavaScript 框架,包括 Vue。
CSS 领域正朝着多个方向发展,因为要解决许多挑战并且没有“正确”的路径。我一直在花费大量精力尝试各种方法,主要是在个人项目上,所以这个系列的目的只是告知,而不是给你解决方案。
CSS 的挑战
在深入研究代码之前,有必要解释 Web 应用程序样式化方面最显着的挑战。我将在本系列中讨论的是范围,条件和动态样式以及可重用性。
作用域
作用域定是众所周知的 CSS 挑战,它的目的是编写不会影响到组件外部的样式,从而避免意外的副作用。我们希望在不影响编码体验的情况下实现功能。
条件和动态样式
虽然前端应用程序中的状态开始变得越来越先进,但 CSS 仍然是静态的。我们只能有条件地应用样式集 – 如果按钮是主要的,我们可能会应用“primary”类并在单独的 CSS 文件中定义它的样式以应用它在屏幕上的样式。有几个预定义的按钮变化是可管理的,但如果我们想要有各种按钮,如为 Twitter,Facebook,Pinterest 定制的特定按钮,可能还会有其他很多种?我们真正想要做的只是传递颜色并使用 CSS 定义状态,如悬停,焦点,禁用等。这称为动态样式,因为我们不再在预定义样式之间切换 – 我们不知道接下来会发生什么。可能会想到内联样式来解决此问题,但它们不支持伪类,属性选择器,媒体查询等。
可重用性
重用规则集,媒体查询等是我最近很少看到的一个主题,因为它已经被 Sass 和 Less 等预处理器解决了。但是我仍然想在这个系列中再次提起它。
我将列出一些处理这些挑战的技术以及它们在本系列的两个部分中的局限性。没有任何技术优于其他技术,它们甚至不相互排斥; 您可以选择一个或组合它们,具体取决于您的决定是否能改善您的项目质量。
开始吧
我们将使用名为 Photo 的示例组件演示不同的样式技术。我们将呈现可能具有圆角的响应式图像,同时将替代文本显示为标题。它会像这样使用:
<Photo publicId=”balloons” alt=”Hot air balloons!” rounded />
在构建实际组件之前,我们将抽象出 srcSet 属性以保持示例代码简洁。那么,让我们创建一个带有两个实用程序的 utils.js 文件,用于使用 Cloudinary 生成不同宽度的图像:
import {Cloudinary} from ‘cloudinary-core’

const cl = Cloudinary.new({cloud_name: ‘demo’, secure: true})

export const getSrc = ({publicId, width}) =>
cl.url(publicId, { crop: ‘scale’, width})

export const getSrcSet = ({publicId, widths}) => widths
.map(width => `${getSrc({ publicId, width})} ${width}w`)
.join(‘, ‘)
我们设置 Cloudinary 实例以使用 Cloudinary 的演示云名称,以及根据指定选项为图像 publicId 生成 URL 的 url 方法。我们只对修改此组件的宽度感兴趣。
我们将分别将这些实用程序用于 src 和 srcset 属性:
getSrc({publicId: ‘balloons’, width: 200})
// => ‘https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons’

getSrcSet({publicId: ‘balloons’, widths: [200, 400] })
// => ‘https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons 200w,
https://res.cloudinary.com/demo/image/upload/c_scale,w_400/balloons 400w’
如果你不熟悉 srcset 和 sizes 属性,我建议先阅读一下有关响应式图像的内容。这样,您可以更轻松地按照示例进行操作。
CSS-in-JS
CSS-in-JS 是一种样式方法,它将 CSS 模型抽象到组件级别,而不是文档级别。这个想法是 CSS 可以限定为特定组件 – 并且只限于该组件 – 以使这些特定样式不与其他组件共享或泄露到其他组件,并且仅在需要时才调用。CSS-in-JS 库通过在 <head> 中插入 <style> 标签在运行时创建样式。
使用这个概念的第一个库是 JSS。以下是使用其语法的示例:
import React from ‘react’
import injectSheet from ‘react-jss’
import {getSrc, getSrcSet} from ‘./utils’

const styles = {
photo: {
width: 200,
‘@media (min-width: 30rem)’: {
width: 400,
},
borderRadius: props => (props.rounded ? ‘1rem’ : 0),
},
}

const Photo = ({classes, publicId, alt}) => (
<figure>
<img
className={classes.photo}
src={getSrc({ publicId, width: 200})}
srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}
sizes=”(min-width: 30rem) 400px, 200px”
/>
<figcaption>{alt}</figcaption>
</figure>
)
Photo.defaultProps = {
rounded: false,
}

export default injectSheet(styles)(Photo)
乍一看,样式对象看起来像用对象表示法编写的 CSS,带有附加功能,比如传递一个函数来设置基于 props 的值。生成的类是唯一的,因此您永远不必担心它们与其他样式冲突。换句话说,你可以自由的使用作用域!这就是大多数 CSS-in-JS 库的工作方式 – 当然,我们将在功能和语法方面进行一些改进。
您可以通过属性看到渲染图像的宽度从 200px 开始,然后当视口宽度变为至少 30rem 时,宽度增加到 400px 宽。我们生成了额外的 800 宽度,以覆盖更大的屏幕密度:

1x screens 使用 200 and 400
2x screens 使用 400 and 800

styled-components 是另一个 CSS-in-JS 库,但是使用更熟悉的语法巧妙地使用模板文字而不是对象看起来更像 CSS:
import React from ‘react’
import styled, {css} from ‘styled-components’
import {getSrc, getSrcSet} from ‘./utils’

const mediaQuery = ‘(min-width: 30rem)’

const roundedStyle = css`
border-radius: 1rem;
`

const Image = styled.img`
width: 200px;
@media ${mediaQuery} {
width: 400px;
}
${props => props.rounded && roundedStyle};
`

const Photo = ({publicId, alt, rounded}) => (
<figure>
<Image
src={getSrc({ publicId, width: 200})}
srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}
sizes={`${mediaQuery} 400px, 200px`}
rounded={rounded}
/>
<figcaption>{alt}</figcaption>
</figure>
)
Photo.defaultProps = {
rounded: false,
}

export default Photo
我们经常创建语义中性元素,如 <div> 和 <span>,仅用于样式目的。这个库以及许多其他库允许我们在一个动作中创建和设置它们。
我最喜欢这种语法的好处是它就像常规的 CSS,减去插值。这意味着我们可以更轻松地迁移 CSS 代码,并且我们可以使用现有的 css 知识,而不必熟悉在对象语法中编写 CSS。
请注意,我们可以在我们的样式中插入几乎任何东西。此特定示例演示了如何将媒体查询保存在变量中并在多个位置重用它。响应式图像是一个很好的用例,因为 sizes 属性基本上包含 CSS,所以我们可以使用 JavaScript 来使代码更简洁。
假设我们决定在视觉上隐藏字幕,但仍然可以让屏幕阅读器访问它。我知道实现这一目标的更好方法是使用 alt 属性,但为了这个例子,让我们使用不同的方式。我们可以使用一个名为 polished 的样式 mixin 库 – 它适用于 CSS-in-JS 库,非常适合我们的示例。这个库包含一个名为 hideVisually 的 mixin,它正是我们想要的,我们可以通过插入它的返回值来使用它:
import {hideVisually} from ‘polished’

const Caption = styled.figcaption`
${hideVisually()};
`

<Caption>{alt}</Caption>
即使 hideVisually 输出一个对象,样式组件库也知道如何将其作为样式进行插值。
CSS-in-JS 库具有许多高级功能,如主题,供应商前缀甚至内联关键 CSS,这使得完全停止编写 CSS 文件变得容易。此时,您可以开始了解为什么 CSS-in-JS 成为一个诱人的概念。
缺点和局限
CSS-in-JS 的明显缺点是它引入了一个运行时:需要通过 JavaScript 加载,解析和执行样式。CSS-in-JS 库的作者正在添加各种智能优化,如 Babel 插件,但仍然存在一些运行时成本。
同样重要的是要注意 PostCSS 没有解析这些库,因为 PostCSS 不是设计用于运行时的。许多人使用 stylis 作为结果,因为它更快。这意味着我们遗憾的是无法使用 PostCSS 插件。
我要提到的最后一个缺点是工具。CSS-in-JS 正在以非常快的速度发展,文本编辑器扩展,linters,代码格式化等等需要追赶新功能以保持同等水平。例如,人们正在使用 VS Code 扩展样式组件来表示类似情感的 CSS-in-JS 库,即使它们并非都具有相同的功能。我甚至看到提议功能的 API 选择受到保留语法突出显示的目标的影响!
未来
有两个新的 CSS-in-JS 库,Linaria 和 astroturf,它们通过将 CSS 提取到文件中来管理零运行时。它们的 API 类似于样式组件,但它们的功能和目标各不相同。
Linaria 的目标是通过内置函数(如作用域,嵌套和供应商前缀)来模仿 CSS-in-JS 库的 API,如样式组件。相反,astroturf 是基于 CSS 模块构建的,具有有限的插值功能,并鼓励使用 CSS 生态系统而不是使用 JavaScript。
结论
CSS-in-JS 是一体化的样式解决方案,用于弥合 CSS 和 JavaScript 之间的差距。它们易于使用,并且包含有用的内置优化 – 但所有这些都需要付出代价。最值得注意的是,通过使用 CSS-in-JS,我们基本上从 CSS 生态系统中退出并使用 JavaScript 来解决我们的问题。
零运行时解决方案通过恢复 CSS 工具来缓解一些缺点,这些工具将 CSS-in-JS 讨论提升到更有趣的水平。与 CSS-in-JS 相比,预处理工具的实际限制是什么?这将在本系列的下一部分中介绍。

正文完
 0