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的示例组件演示不同的样式技术。 我们将呈现可能具有圆角的响应式图像,同时将替代文本显示为标题。 它会像这样使用:
代码语言:javascript复制<Photo publicId="balloons" alt="Hot air balloons!" rounded />
在构建实际组件之前,我们将抽象出srcSet属性以保持示例代码简洁。 那么,让我们创建一个带有两个实用程序的utils.js文件,用于使用Cloudinary生成不同宽度的图像:
代码语言:javascript复制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属性:
代码语言:javascript复制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。 以下是使用其语法的示例:
代码语言:javascript复制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:
代码语言:javascript复制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,它正是我们想要的,我们可以通过插入它的返回值来使用它:
代码语言:javascript复制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相比,预处理工具的实际限制是什么? 这将在本系列的下一部分中介绍。