【譯】統一樣式語言

zhe.zhang發表於2017-06-28

統一樣式語言

在過去幾年中,我們見證了 CSS-in-JS 的興起,尤其是在 React 社群。但它也飽含爭議,很多人,尤其是那些已經精通 CSS 的人,對此持懷疑態度。

"為什麼有人要在 JS 中寫 CSS?

這簡直是一個可怕的想法!

但願他們學過 CSS !"

如果這是你聽到 CSS-in-JS 時的反應,那麼請閱讀下去。我們來看看為什麼在 JavaScript 中編寫樣式並不是一個可怕的想法,以及為什麼我認為你應該長期關注這個快速發展的領域。

相互誤解的社群

React 社群經常被 CSS 社群誤解,反之亦然。對我來說這很有趣,因為我同時混跡於這兩個社群。

我從九十年代後期開始學習 HTML,並且從基於表格佈局的黑暗時代就開始專職於 CSS。受 CSS 禪意花園啟發,我是最早一批將現有程式碼向語義化標籤和層疊樣式表遷移的開發者。不久後我開始痴迷於 HTML 和 JavaScript 的分離工作,在伺服器渲染出來的頁面中使用非侵入式 JavaScript 同客戶端互動。圍繞這些實踐,我們組成了一個非常小但是充滿活力的社群,並且我們成為了第一代前端開發人員,努力去解決各個瀏覽器的相容性問題。

在這種關注於web的背景下,你可能會認為我會強烈反對 React 的 HTML-in-JS 模式,它似乎違背了我們所堅持的原則,但實際上恰恰相反。根據我的經驗,React 的元件化模型結合其服務端渲染的能力,終於為我們提供了一種構建大規模複雜單頁應用的方式,並且仍然能將快速、易訪問、漸進增強的應用推送給我們的使用者。在我們的旗艦產品 SEEK 上我們就是這麼做的,它是一個 React 單頁應用,當 JavaScript 被禁用時,其核心搜尋流程依然可用,因為我們通過在伺服器端執行同構的 JavaScript 程式碼來實現優雅降級。

所以,請考慮將這篇文章作為兩個社群之間相互示好的橄欖枝。讓我們一起努力理解 CSS-in-JS 這次轉變的實質所在。也許它不完美,也許你沒有計劃在你的產品中使用這門技術,也許它對你不是很有說服力,但是至少值得你嘗試思考一下。

為什麼要使用 CSS-in-JS?

如果你熟悉我最近做的與 React 以及 CSS Modules相關的工作,你會驚訝地發現我是捍衛 CSS-in-JS 的。

畢竟,通常那些希望樣式有區域性作用域但是又不希望在 JS 中寫 CSS 的開發者才會選擇使用 CSS Modules。事實上,我甚至在自己的工作中都不使用CSS-in-JS。

儘管如此,我仍然對 CSS-in-JS 社群保持濃厚的興趣,對他們不斷提出的創新保持密切關注。不僅如此,我認為這些應該同樣被更多的 CSS 社群所關注

原因是什麼呢?

為了更清楚地瞭解為什麼人們選擇在 JavaScript 中編寫樣式,我們將重點關注這種方式所帶來的實際性好處.

我把這些優點分為五個主要方面:

  1. 擁有作用域的樣式
  2. 抽取關鍵 CSS
  3. 更智慧的優化
  4. 打包管理
  5. 在非瀏覽器環境下的樣式

讓我們做進一步的瞭解,仔細看看 CSS-in-JS 在這幾個方面分別帶來了什麼。

1.

擁有作用域的樣式

眾所周知,想要在大規模專案中高效地構建 CSS 是非常困難的。當加入一個需要長期維護的專案時,我們通常會發現 CSS 是系統中最複雜的部分。

為了解決這個問題,CSS 社群已經投入了巨大的努力,通過採用 Nicole Sullivan 提出的 OOCSSJonathan Snook 提出的 SMACSS 都可以提高我們樣式的可維護性。但是目前就流行程度而言,最佳的選擇毫無爭辯是 Yandex 提出的 BEM (Block Element Modifier)。

從根本上來說,BEM (純粹用於 CSS 時)只是一個命名規範,它要求樣式的類名要遵守 .Block__element--modifier 的模式。在任何使用 BEM 風格的程式碼庫中,開發人員必須始終遵守 BEM 的規則。當被嚴格遵守時,BEM 的效果很好,但是為什麼像作用域這種基礎的東西,卻只使用純粹的命名規範來限制呢?

無論是否有明確表示,大多數 CSS-in-JS 類庫的思路和 BEM 都很相似,它們努力將樣式獨立作用於單個 UI 元件,只不過他們用了完全不同的實現方式。

那麼在實際程式碼中是什麼樣子呢?當使用 Sunil Pai 開發的 glamor 時,程式碼看起來像下面這樣:

import { css } from 'glamor'
const title = css({
  fontSize: '1.8em',
  fontFamily: 'Comic Sans MS',
  color: 'blue'
})
console.log(title)
// → 'css-1pyvz'複製程式碼

你可能會注意到這段程式碼中沒有 CSS 類。樣式不再是對系統其他地方定義的 class 的硬編碼引用,而是由我們的工具庫自動生成的。我們不必再擔心選擇器會在全域性作用域裡發生衝突,這也意味著我們不再需要替他們新增字首了。

這個選擇器的作用域與上下文程式碼的作用域一致。如果你希望在你應用的其他部分使用這個規則,你就需要將它轉換成 JavaScript 模組並且在需要使用的地方引用它。就保持程式碼庫的可維護性而言,這是非常強大的,它確保了任何給定的樣式都可以像其他程式碼一樣容易追蹤來源

從僅僅靠命名約定來限制樣式的作用域到預設強制區域性作用域樣式轉變,我們已經提升了樣式的基本能力。BEM 的功能已經被預設使用了,而不再是一個可選項。

在我繼續之前,我要澄清至關重要的一點。

它生成的是真正的 CSS,而不是內聯樣式

大多數早期的 CSS-in-JS 庫都是將樣式直接內聯到每個元素上,但是這種模式有個嚴重的缺陷:'style' 屬性並不能勝任所有 CSS 的功能。大多數新的 CSS-in-JS 庫則側重於動態樣式表,在執行時從一個全域性樣式集中插入和刪除規則。

舉個例子,讓我們看看由 Oleg Slobodskoi 開發的 JSS,這是最早生成真正 CSS 的 CSS-in-JS 庫之一。

使用 JSS 時,你可以使用標準的 CSS 特性,比如 hover 和媒體查詢,它們會對映成相應的 CSS 規則。

const styles = {
  button: {
    padding: '10px',
    '&:hover': {
      background: 'blue'
    }
  },
  '@media (min-width: 1024px)': {
    button: {
      padding: '20px'
    }
  }
}複製程式碼

將這些樣式插入到文件中後,你就可以使用那些自動生成的類名。

const { classes } = jss.createStyleSheet(styles).attach()複製程式碼

不管你是使用一個完整的框架,還是簡單粗暴地使用 innerHTML,當用 JavaScript 生成 HTML 時,都可以使用這些生成的 代替硬編碼的類名。

document.body.innerHTML = `
  <h1 class="${classes.heading}">Hello World!</h1>
`複製程式碼

但是單獨使用這種方式管理樣式並沒有帶來很大的優勢,它通常需要和一些元件庫搭配使用。因此,可以很容易找到適用於目前最流行庫的繫結方案。例如,JSS 可以通過 react-jss 的幫助輕鬆地繫結到 React 元件上,在管理生命週期的同時,它可以幫你給每個元件插入一個小的樣式集。

import injectSheet from 'react-jss'
const Button = ({ classes, children }) => (
  <button className={classes.button}>
    <span className={classes.label}>
      {children}
    </span>
  </button>
)
export default injectSheet(styles)(Button)複製程式碼

通過程式碼層面上的緊密結合將我們的樣式集中到元件上,我們得到了合乎 BEM 邏輯的結果。但是,CSS-in-JS 社群的許多人覺得提取、命名和複用元件的重要性在所有繫結樣式的樣板中都被遺棄了。

Glen MaddernMax Stoiber 提出了一個全新的思路來解決這個問題 —— styled-components

我們強制性地直接建立元件,而不是建立樣式然後再手動地將他們繫結到元件上。

import styled from 'styled-components'

const Title = styled.h1`
  font-family: Comic Sans MS;
  color: blue;
`複製程式碼

在應用這些樣式時,我們不會將 class 新增到一個現有的元素上,而是簡單地渲染這些被生成的元件。

<Title>Hello World!</Title>複製程式碼

styled-components 通過模板字面量的方式來使用傳統的 CSS 語法,但有人更喜歡使用資料結構。來自 PayPalKent C. Dodds 所提供的 Glamorous 是一個值得關注的替代方案。

Glamorous 提供了與 styled-components 類似的元件優先的 API,但是他用物件替代了字串。這樣就無需在庫中引入一個 CSS 解析器,從而可以降低庫的大小並提升效能。

import glamorous from 'glamorous'

const Title = glamorous.h1({
  fontFamily: 'Comic Sans MS',
  color: 'blue'
})複製程式碼

無論你使用哪種語法來描述你的樣式,他們都不再僅僅作用於某個元件,而是成為元件不可分割的一部分。當使用一個像 React 這樣的庫時,元件是基本的構建塊,而現在我們的樣式也成了構建這個架構的核心部分。既然我們能將應用程式中的所有內容都描述為元件,那麼為什麼樣式不行呢?

對於那些有豐富 BEM 開發經驗的工程師來說,我們對系統改造所帶來的提升意義並不是很大。然而事實上,CSS Modules 讓你在不用放棄所熟悉的 CSS 工具生態的同時獲得了這些提升,這也是很多專案堅持使用 CSS Modules 的原因,他們可以在保持其常規 CSS 編碼習慣的同時充分解決編寫大規模 CSS 所遇到的問題。

然而,當我們開始在這些基本概念之上進行構建時,事情開始變得更有趣。

2.

抽取關鍵 CSS

最近,在 document 的頭部內聯關鍵樣式已經成為一種最佳實踐,只提供當前頁面所需的樣式從而降低了首屏渲染時間。這與我們常用的樣式載入方式形成了鮮明對比,之前我們通常會強制瀏覽器在渲染之前下載應用的所有樣式。

雖然像 Addy Osmani 提供的 critical 這類工具可以用於提取和內聯關鍵 CSS,但是他們無法從根本上改變關鍵 CSS 難以維護和自動化的事實。這只是一個可選擇用來做效能優化的奇技淫巧,所以大部分專案似乎放棄了這一步。

CSS-in-JS 則完全不同。

當你的應用使用服務端渲染時,提取關鍵 CSS 將不僅僅是優化,而是伺服器端 CSS-in-JS 的首要工作。

舉個例子,當使用 Khan Academy 開發的 Aphrodite 時,可以通過它的 css 函式來跟蹤在這次渲染過程中使用的樣式,並且將生成的 class 內聯到元素上。

import { StyleSheet, css } from 'aphrodite'
const styles = StyleSheet.create({
  title: { ... }
})
const Heading = ({ children }) => (
  <h1 className={css(styles.heading)}>{ children }</h1>
)複製程式碼

即便你所有的樣式都是在 JavaScript 中定義的,你也可以很輕鬆地提取當前頁面所需要的所有樣式並生成一個 CSS 字串,在執行服務端渲染時將它們插入到 document 的頭部。

import { StyleSheetServer } from 'aphrodite';

const { html, css } = StyleSheetServer.renderStatic(() => {
  return ReactDOMServer.renderToString(<App/>);
});複製程式碼

現在你可以像這樣渲染你的關鍵 CSS 程式碼塊:

const criticalCSS = `
  <style data-aphrodite>
    ${css.content}
  </style>
`;複製程式碼

如果你研究過 React 的服務端渲染模型,你可能會發現這個模式非常眼熟。在 React 中,你的元件是在 JavaScript 中定義他們的標籤的,但卻可以在伺服器端渲染成常規的 HTML 字串。

如果你使用漸進增強的方式構建你的應用,即便整個專案可能全部是用 JavaScript 寫的,客戶端也可能根本就不需要 JavaScript。

不管怎樣,對於客戶端執行的程式碼而言,其打包後的 bundle 都要包含啟動單頁應用所需要的程式碼。這些程式碼可以讓頁面瞬間活起來,瀏覽器中的渲染也是從這裡開始的。

由於在伺服器上渲染 HTML 和 CSS 是同時進行的,正如前面的例子所示,像 Aphrodite 這樣的庫通常會以一個函式呼叫的方式幫助我們流式生成關鍵 CSS 和服務端渲染的 HTML。現在,我們可以用類似的方式將我們的 React 元件渲染成靜態 HTML。

const appHtml = `
  <div id="root">
    ${html}
  </div>
`複製程式碼

通過在伺服器端使用 CSS-in-JS,我們的單頁應用不僅可以脫離 JavaScript 工作,它甚至可以渲染的更快

正如有作用域的 CSS 選擇器一樣,渲染關鍵 CSS 這個最佳實踐現在也是預設具備的能力了,而不是被選擇性使用的

3.

更智慧的優化

我們最近看到了構建 CSS 的新方式的興起,比如 YahooAtomic CSSAdam MorseTachyons,它們更推薦使用短小的、單一用途的 class,而不是語義化的 class。舉個例子,當使用 Atomic CSS 時,你將使用類似於函式呼叫的語法來新增類名,並且它們會被用來生成合適的樣式表。

<div class="Bgc(#0280ae.5) C(#fff) P(20px)">
  Atomic CSS
</div>複製程式碼

這種做法的目的是通過最大化地提高 class 的複用性,以及有效地將 class 像內聯樣式一樣對待,lai確保打包出來的 CSS 儘可能的精簡。雖然檔案大小的減少很容易體現,但對於你的程式碼庫和團隊成員的影響似乎是微乎其微的。不過這些包含了對 CSS 和 HTML 更改的優化,由於其自身性質,成就了一個更具意義的架構。

正如我們之前介紹的那樣,當使用 CSS-in-JS 或者 CSS Modules 時,你不再需要在 HTML 中硬編碼你的 class,而是動態引用由庫或者構建工具自動生成的 JavaScript 值。

我們不再這樣寫樣式:

<aside className="sidebar" />複製程式碼

而是這樣:

<aside className={styles.sidebar} />複製程式碼

這個變化表面上看起來也許沒什麼,但是從如何管理標記語言和樣式之間的關係上來說,這卻是一個里程碑式的改變。通過給予我們的 CSS 工具修改樣式的能力,尤其是修改最終應用到元素上的 class 的能力,我們為樣式表解鎖了一個全新的優化方式。

如果看看上面的例子,就會發現 "styles.sidebar" 對應了一個字串,但並沒有限制它只能是一個單獨的 class。我們都知道,它可以很容易地成為一個包含十幾個 class 的字串。

<aside className={styles.sidebar} />
// Could easily resolve to this:
<aside className={'class1 class2 class3 class4'} />複製程式碼

如果我們可以優化我們的樣式,為每一套樣式生成多個 class,我們就可以做一些真正有趣的事。

我最喜歡的例子是 Ryan Tsao 編寫的 Styletron

就像 CSS-in-JS 和 CSS Modules 自動新增 BEM 風格的字首一樣,Styletron 對 Atomic CSS 做了同樣的事情。

它的核心 API 只專注於一件事 —— 為每個由屬性、值、媒體查詢組合起來的樣式定義一個單獨的 CSS 規則,然後返回一個自動生成的 class。

import styletron from 'styletron';
styletron.injectDeclaration({
  prop: 'color',
  val: 'red',
  media: '(min-width: 800px)'
});
// → 'a'複製程式碼

當然,Styletron 也提供了一些高階 API,比如它的 injectStyle 函式允許一次定義多個規則。

import { injectStyle } from 'styletron-utils';
injectStyle(styletron, {
  color: 'red',
  display: 'inline-block'
});
// → 'a d'
injectStyle(styletron, {
  color: 'red',
  fontSize: '1.6em'
});
// → 'a e'複製程式碼

請注意上面生成的兩組類名之間的相同點。

通過放棄對 class 本身的低階控制,而僅定義所需要的樣式,就可以讓工具庫幫我們生成最佳的原子 class 集合。

過去我們只能通過手工查詢的方式將樣式拆分成可複用的 class,現在已經可以完全自動化的完成這種優化了。你應該也開始注意到這種趨勢了。原子 CSS 已經是預設具備的能力,而不再是被選擇性使用的

4.

打包管理

在深入討論這一點之前,我們先停下來思考一個看似簡單的問題。

我們如何相互分享 CSS?

我們已經從手動下載 CSS 檔案轉變為使用像 Bower 這種前端特定的包管理工具,現在則可以通過 npm 使用 Browserifywebpack。雖然這些工具已經可以自動引入外部依賴包裡的 CSS,但是目前前端社群大多還是手動處理 CSS 的依賴關係。

無論使用哪種方式,你得清楚一件事:CSS 之間的依賴並不是很好處理。

正如許多人還記得的一樣,在使用 Bower 和 npm 管理 JavaScript 模組時,出現過類似的情況。

Bower 沒有指定任何特定的模組格式,而釋出到 npm 的模組則要求使用 CommonJS 模組格式。這種不一致,對釋出到每個平臺的包數量產生了巨大的影響。

規模小但是有巢狀依賴關係的模組更願意使用 npm,Bower 則吸引了大型而又獨立的模組,其中可能也就有兩三個模組,再加幾個外掛。由於在 Bower 中你的依賴關係沒有一個模組系統去作支撐,每個包無法輕鬆地利用它自己的依賴關係,所以在整合這一塊,基本上就留給開發者手動去操作了。

因此,隨著時間的推移,npm 上的模組數量呈指數性增長,而 Bower 只能是有限的線性增長。雖然這可能是各種原因導致的,但很公平地說,主要還是由每個平臺是否允許模組在執行時互相引用導致的。

不幸的是,對於 CSS 社群來說這太熟悉了,我們發現相對於 npm 上的 JavaScript 包來說,獨立的 CSS 模組的數量也增長的很慢。

如果我們也想實現 npm 的指數增長呢?如果我們想依賴不同大小不同層次的複雜模組,而不是專注於大型、全面的框架呢?為了做到這一點,我們不僅需要一個包管理器,還需要一個合適的模組格式。

這是否意味著我們需要專門為 CSS 或者 Sass 和 Less 這樣的前處理器設計一個包管理工具?

真正有趣的是,我們已經通過 HTML 進行了類似的實現。如果你就如何分享 HTML 問我類似的問題,你可能馬上就會意識到,我們幾乎不會直接分享原始的 HTML —— 我們分享 HTML-in-JS

我們通過 jQuery 外掛Angular 指令React 元件來實現這個功能。我們的大元件是由一些獨立釋出在 npm 上,包含自己 HTML 的小元件組成的。原生 HTML 格式也許沒有這種能力,但是通過將 HTML 嵌入到完整的程式語言中,我們就可以很輕鬆的突破這個限制。

如果我們像 HTML 那樣,通過 JavaScript 去分享以及生成 CSS 呢?能不能使用返回物件和字串的函式而不是使用 mixins ?又或者我們利用 Object.assign 和新的 object spread 操作符merge 物件而不是用 extending classes 呢?

const styles = {
  ...rules,
  ...moreRules,
  fontFamily: 'Comic Sans MS',
  color: 'blue'
}複製程式碼

一旦我們開始用這種方式編寫我們的樣式,我們就可以使用相同的模式、相同的工具、相同的基礎架構、相同的生態系統來編寫和分享我們的樣式程式碼,就像我們應用程式中的任何其他程式碼一樣。

Max StoiberNik GrafBrian Hough 提供的 Polished 就是一個你如何從中受益的良好示例。

Polished 就像是 CSS-in-JS 界的 Lodash,它提供了一整套完整的 mixins、顏色函式、一些速寫方法等等,使得那些使用 Sass 的開發者可以熟練地在 JavaScript 中編寫樣式。現在有一個最大的區別就是這些程式碼在複用、測試和分享方面,都提高了一個層級,並且能夠完整的使用 JavaScript 模組生態系統。

那麼,當談到 CSS 時,我們如何獲得和 npm 上其他模組相似的開源程度,以及如何用一些小的可複用的開源包組合成大型樣式集合?奇怪的是,我們最終可以通過將我們的 CSS 嵌入另一種語言並且完全擁抱 JavaScript 模組實現了這一點。

5.

在非瀏覽器環境下的樣式

到目前為止,我的文章已經涵蓋了所有的要點,雖然在 JavaScript 中編寫 CSS 會更加便捷,但是常規的 CSS 也可以實現這些功能。這也是我把最有趣、最面向未來的一點留到現在的原因。也許它不一定能在如今的 CSS-in-JS 社群中發揮巨大的作用,但它可能會成為設計領域未來發展的基石。它不僅會影響開發人員,也會影響設計師,最終它將改變這兩個領域相互溝通的方式。

為了引入它,我先簡單介紹一下 React。

React 的理念是用元件作為最終渲染的中間層。在瀏覽器中工作時,我們構建複雜的虛擬 DOM 樹而不是直接操作 DOM 元素。

有趣的是,DOM 渲染相關的程式碼並不屬於 React 的核心部分,而是由 react-dom 提供的。

import { render } from 'react-dom'複製程式碼

儘管最初 React 是為 DOM 設計的,並且大部分情況下還是在瀏覽器中使用,但是這種模式也允許 React 通過簡單地引入新的渲染引擎就能從容面對各種不同的使用環境。

JSX 不僅僅可以用於虛擬 DOM,他可以用在任何的虛擬檢視上。

這就是 React Native 的工作原理,我們通過編寫那些渲染成 native 的元件以實現用 JavaScript 編寫真正的 native 應用,比如我們用 ViewText 取代了 divspan

從 CSS 的角度來看,React Native 最有趣的就是它擁有自己特有的 StyleSheet API

var styles = StyleSheet.create({
  container: {
    borderRadius: 4,
    borderWidth: 0.5,
    borderColor: '#d6d7da',
  },
  title: {
    fontSize: 19,
    fontWeight: 'bold',
  },
  activeTitle: {
    color: 'red',
  }
})複製程式碼

這裡你會看到一組熟悉的樣式,在這種情況下可以覆蓋顏色、字型和邊框樣式。

這些規則都非常簡單,並且很容易對映到大部分的 UI 環境上,但是當涉及到 native 佈局時,事情就變得非常有趣了。

var styles = StyleSheet.create({
  container: {
    display: 'flex'
  }
})複製程式碼

儘管執行在瀏覽器環境之外,React Native 有自己的 flexbox 的 native 實現

最初發布時它是一個名為 css-layout 的 JavaScript 模組,完全用 JavaScript 重新實現了 flexbox(包含充分的測試),為了更好的可移植性它現在已經遷移到 C 語言。

鑑於這個專案的影響力和重要性,它被賦予了一個獨立的重要品牌 ——— Yoga

即使 Yoga 完全是為了把 CSS 概念移植到非瀏覽器環境而生,但通過僅僅專注 CSS 特性的子集,它已經統治了一些潛在的其他領域。

"Yoga 的重點是成為一個有表現力的佈局框架,而不是去實現一套完整的 CSS"

這看起來似乎很難實現,但是當你回顧 CSS 體系的歷史時會發現使用 CSS 進行規模化的工作就是選擇一個合適的語言子集

在 Yoga 的例子裡,他們避免了層疊樣式,因為這樣有利於控制樣式的作用域,並且將佈局引擎完全集中在 flexbox 上。雖然這樣會喪失很多功能,但它也為那些需要嵌入樣式的跨平臺元件創造了驚人的機會,我們已經發現幾個試圖利用這個特性的開源專案。

Nicolas Gallagher 開發的 React Native for Web 旨在成為 react-native 的一個替代品。當使用 webpack 這類打包工具時,可以利用 alias 輕鬆替換第三方庫。

module: {
  alias: {
    'react-native': 'react-native-web'
  }
}複製程式碼

使用 React Native for Web 後可以在瀏覽器環境中使用 React Native 元件,包括 React Native StyleSheet API 的瀏覽器部分。

同樣,Leland Richardson 開發的 react-primitives 也提供了一套跨平臺的基礎元件集合,它根據目標平臺來抽象具體的實現細節,為跨平臺元件創造可行的標準。

甚至 微軟 也推出了 ReactXP,這個庫旨在簡化跨 web 和 native 的工作流,它也有自己的跨平臺樣式實現

即使你不為 native 應用程式編寫程式碼,也有很重要的一點要注意:擁有一個真正的跨平臺的元件抽象,能夠幫我們有針對性地應對各種各樣的環境,有時你都無法預測會遇到哪些情況。

我所見過的最令人震驚的例子是 AirbnbJon Gold 開發的 react-sketchapp

我們中很多人都花費了大量時間去嘗試標準化我們的設計語言,並且儘可能的避免系統中的重複部分。不幸的是,儘管我們希望樣式是唯一的,但我們最少也會有兩個來源 —— 開發人員的動態樣式以及設計師的靜態樣式。雖然這已經比我們之前的模式好了很多,但是它仍然需要我們手工的將樣式從 Sketch 這樣的設計工具同步到程式碼裡。這也是 react-sketchapp 被開發出來的原因。

感謝 Sketch 的 JavaScript API,以及 React 與不同渲染引擎相連的能力,react-sketchapp 讓我們可以利用跨平臺的 React 元件並在 Sketch 文件裡渲染他們。

不必多說,這很可能改變設計師和開發人員的合作方式。現在,當我們對設計進行迭代時,無論在設計工具還是開發者工具上,我們都可以通過相同的宣告引用同一個元件。

通過 Sketch 中的 symbolsReact 中的元件,我們的行業從本質上開始匯合成同一個抽象,並且通過分享相同的工具我們可以更緊密的協作。

這麼多新的嘗試都來自 React 和其周邊的社群,這並不是巧合。

在元件架構中,優先順序最高的就是將元件的關注點集中在一起。這自然包括它的區域性作用域樣式,也要感謝 RelayApollo 這兩個庫,他們讓我們可以往資料獲取這些更復雜的方向延伸。結果就是他們釋放了巨大的潛力,而我們現在所瞭解的,只是其中冰山一角。

這對我們的樣式編寫以及架構中的任何部分都產生了積極的影響。

通過將我們開發元件的模式統一到單一語言上,我們能夠從功能上,而不是從技術上,將我們的關注點進行更好的分離。比如我們可以將元件的所有內容都限制在自己的作用域內,從他們擴充套件成大型的可維護的系統,用之前無法使用的方式進行優化,更便捷的分享我們的工作,以及利用小型開源模組構建大型應用程式。更重要的是,我們依然遵循漸進增強的理念,也不會放棄那些被認為是認真對待 web 平臺的理念。

最重要的是,我對使用單一語言編寫出的元件的潛力感到興奮,他們形成了一種新的、統一的樣式語言基礎,並以一種前所未有的方式統一了前端社群。

在 SEEK,我們正在努力利用這一特性,我們圍繞元件模型來構建線上樣式指南,其中語義化、互動和視覺風格都統一在一個單獨的抽象中。這形成了開發人員和設計師之間共享的通用設計語言。

構建一個頁面應該儘可能的和拼裝元件一樣簡單,這樣可以確保我們的工作保持較高的質量,並且允許我們在產品上線很久後也有能力去升級其設計語言。

import {
  PageBlock,
  Card,
  Text
} from 'seek-style-guide/react'
const App = () => (
  <PageBlock>
    <Card>
      <Text heading>Hello World!</Text>
    </Card>
  </PageBlock>
)複製程式碼

儘管我們的樣式指南是用 React、webpack 和 CSS Modules 構建的,但該架構恰好反映了在使用 CSS-in-JS 構建的任何系統中您都需要注意哪些。技術選型可能有不同,但是核心理念是一樣的。

然而,未來這些技術選型可能會以一種意想不到的方式進行轉變,因此關注這個領域對於我們元件生態系統的持續發展至關重要。我們現在可能不會用 CSS-in-JS 這項技術,但是很可能沒過多久就會出現一個令人信服的理由讓我們使用它。

CSS-in-JS 在短時間裡已經有了出人意料的發展,但更重要的是,它只是這個巨集偉藍圖的開始。

它還有很大的改進空間,並且它的創新還沒有停止的跡象。新的庫正不斷湧現,它們解決了未來會出現的問題並且提升了開發人員的體驗 —— 比如效能的提升、在構建時抽取靜態 CSS、支援 CSS 變數以及降低了前端開發人員的入門門檻。

這也是 CSS 社群的准入門檻。無論他們對我們的工作流程有多大的改動,都不會改變你仍然需要學習 CSS 的事實

我們可能使用不同的語法,也可能以不同的方式構建我們的應用,但是 CSS 的基本構建塊不會改變。同樣,我們行業向元件架構的轉變是不可避免的,通過這種方式重新構思前端開發的意願只會越來越強烈。我們非常需要共同合作以確保我們的解決方案可以廣泛適用於各種背景的開發人員,無論是專注於設計的,工程的或者對這兩方面都很關注的開發者。

雖然有時我們的觀點不一致,但是 CSS 和 JS 社群對於改進前端,更加認真地對待 Web 平臺以及改進我們下一代 web 開發流程都有很大的熱情。社群的潛力是巨大的,而且到目前為止,儘管我們已經解決了大量的問題,仍然有很多工作還沒有完成。

到這裡,可能你依然沒有被說服,但是沒關係。雖然現在在工作上使用 CSS-in-JS 並不是很合理,但我希望它有合適的原因,而不是僅僅因為語法就反對它。

無論如何,未來幾年這種編寫樣式的方式可能會越來越流行,並且值得關注的是它發展的非常快。我衷心希望你可以加入我們,無論是通過貢獻程式碼還是簡單地參與我們的對話討論,都能使下一代 CSS 工具儘可能有效地服務於所有前端開發人員。或者,至少我希望我已經讓你們瞭解了為什麼人們對這一塊如此飽含激情,或者,至少了解為什麼這不是一個愚蠢的點子。

這篇文章是我在德國柏林參加 CSSconf EU 2017 做相同主題演講時撰寫的,並且現在可以在 YouTube 上看到相關視訊。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章