css-in-js 探討

Web開發是須要掌握多種技術。咱們習慣於與多種語言密切合做。並且,隨着開發Web應用程序變得愈來愈廣泛和差異細微化,咱們常常尋找創造性的方法來彌合這些語言之間的差距,從而使咱們的開發環境和工做流程更容易,更高效。css

最多見的示例一般是使用模板語言時。例如,可使用一種語言來生成更詳細的語言(一般是HTML)的代碼。這是前端框架的關鍵做用之一 -操做HTML。這個領域最出名的就是JSX,由於它不是真正的模板語言;它是JavaScript的語法擴展,它使得使用HTML很是簡潔。前端

Web應用程序經歷了許多狀態組合,單獨管理狀態一般頗有挑戰性。這就是爲何CSS有時會被淘汰的緣由 - 即便經過不一樣的狀態和媒體查詢管理樣式一樣重要且一樣具備挑戰性。在這個由兩部分組成的系列中,我想將CSS放在聚光燈下,並探索彌合它與JavaScript之間的差距。在本系列中,我將假設您正在使用像webpack這樣的模塊解析器。所以,我將在個人示例中使用React,但相同或相似的原則適用於其餘JavaScript框架,包括Vue。react

CSS領域正朝着多個方向發展,由於要解決許多挑戰而且沒有「正確」的路徑。我一直在花費大量精力嘗試各類方法,主要是在我的項目上,因此這個系列的目的只是告知,而不是給你解決方案。webpack

CSS的挑戰

在深刻研究代碼以前,有必要解釋Web應用程序樣式化方面最顯着的挑戰。 我將在本系列中討論的是範圍,條件和動態樣式以及可重用性。程序員

做用域

做用域定是衆所周知的CSS挑戰,它的目的是編寫不會影響到組件外部的樣式,從而避免意外的反作用。 咱們但願在不影響編碼體驗的狀況下實現功能。web

條件和動態樣式

雖然前端應用程序中的狀態開始變得愈來愈先進,但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相比,預處理工具的實際限制是什麼? 這將在本系列的下一部分中介紹。

建立了一個程序員交流微信羣,你們進羣交流IT技術

圖片描述

若是已過時,能夠添加博主微信號15706211347,拉你進羣

相關文章
相關標籤/搜索