什麼是 CSS in JS
CSS in JS 解決了 CSS 命名的問題。
由於 component-based 的概念興起元件開發成為顯學,CSS in JS 提供了將樣式寫在元件中解決方案,也讓 CSS 需要從寫程式語言的角度去進行架構設計。
CSS in JS 不僅降低了維護難度也加速了開發速度,減少檔案切換和減少 class 命名錯誤等等問題,常見的 library 像是 css module、vanilla-extract、styled-components、styled-jsx (Next.js) 都非常好上手,更完整的將元件模組化並增加可重用性。
CSS in JS 缺點也要先說在前面,當需要 Server Render 時,相關的效能問題也需要一併考慮進去。
CSS in JS 像是把需要打扮的道具跟裝備直接跟使用的情境整合在一起,像是約會必勝穿搭、適合上班的裝容等等,在遇到相關情境時能夠輕鬆應對。
問題定義
- 當元件在其他專案使用時,搬動除了必須搬完相對應的樣式檔,還必須放在正確的位置
- 在專案最佳化時,樣式檔的引用順序須讓機器不會混淆,若將樣式檔拆太細,很可能發生 Code Splitting 時順序錯誤
優點與解決的問題
- 解決 code spliting 可能出現的順序問題
- 解決命名互相覆蓋,在撰寫樣式檔時也可以用 BEM 的命名規則避開或導入原子化樣式設計
- 維護時不需要再去找到底被哪個樣式檔影響
缺點
- RWD 不像 bootstrap 有格線系統那樣使用方便,所以可以額外導入 CSS 框架的格線系統混合使用
- 較難做效果跟做覆蓋,不同狀態需要不同樣式時要再封裝一層
- 語法的高亮、自動完成、防呆較不完整
- 少了可以共用樣式的好處,像是
@extend
或 @mixin
這樣的寫法,需把元件切的夠小,才會有一定程度的共用性
styled-components
styled-components 提供了在 JavaScript 中直接撰寫 CSS 的介面,意味著可以使用所有的 CSS 功能,切換主視覺要多用一個 <ThemeProvider>
,傳入的 Props 差異太大時有點麻煩
1 2 3 4 5 6 7 8 9 10
| import styled from "styled-components";
function Sample() { const StyledButton = styled.button` color: #ffffff; font-size: 20px; border-radius: 5px; `; return <StyledButton type="button">按鈕</StyledButton>; }
|
套用主視覺配置
styled-components 也提供了主視覺的配置,透過 styled-components 提供的 ThemeProvider 在外層注入就可以在元件中直接取值。
- ThemeProvider 在外層注入視覺配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import { ThemeProvider } from "styled-components";
const colors = { primary: "#c18e00", secondary: "#0086ff", dark: "rgba(0, 0, 0, 0.6)", muted: "rgba(0, 0, 0, 0.3)", light: "rgba(0, 0, 0, 0.18)", };
const fontSizes = { xl: `${1.875 * base}rem`, lg: `${1.375 * base}rem`, md: `${1.25 * base}rem`, base: `${1 * base}rem`, sm: `${0.875 * base}rem`, xs: `${0.75 * base}rem`, };
const theme = { colors, fontSizes, };
class MyApp extends App { componentDidMount() { objectFitImages(); }
render() { const { Component, pageProps, reduxStore } = this.props; return ( <ThemeProvider theme={theme}> <Provider store={reduxStore}> <Component {...pageProps} /> </Provider> </ThemeProvider> ); } }
|
- 在元件中直接取值,甚至可以寫 Function 進行判斷,做出更多元的變化
1 2 3 4 5 6 7 8 9 10 11 12
| const StyleWrapper = styled.div` background: ${({ color, reverse, theme: { colors } }) => reverse ? colors[color] : "none"}; `;
function TestMessage({ color, reverse, message }) { return ( <StyleWrapper color={color} reverse={reverse}> <span>{message}</span> </StyleWrapper> ); }
|
RWD
由於 styled-components 提供了 function 的撰寫模式,所以即使撰寫 RWD 的元件也很簡單。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function getWidthString(span) { let result; if (span) { result = `width: ${(span / 12) * 100}%`; } return result; }
const Column = styled.div` float: left; width: 100%; /* stylelint-disable value-keyword-case */
${({ xs }) => (xs ? getWidthString(xs) : "width: 100%")};
@media only screen and (min-width: 768px) { ${({ sm }) => sm && getWidthString(sm)}; }
@media only screen and (min-width: 992px) { ${({ md }) => md && getWidthString(md)}; }
@media only screen and (min-width: 1200px) { ${({ lg }) => lg && getWidthString(lg)}; } `;
|
styled-jsx
styled-jsx 則是 Next.js 內建支援的介面,基礎的功能也是差不多,就是寫法上稍微不太相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Sample() { return ( <> <style jsx> {` button { color: #ffffff; font-size: 20px; border-radius: 5px; } `} </style> <button type="button">按鈕</button> </> ); }
|
global 的樣式,RWD 之類的就可以訂在這邊。
1 2 3 4 5 6 7 8 9
| export default () => ( <div> <style jsx global>{` body { background: red; } `}</style> </div> );
|
動態的寫法,跟 styled-components 可以做到判斷,但又沒有 function 那麼彈性。
1 2 3 4 5 6 7 8 9 10 11
| const Button = (props) => ( <button> Hello <style jsx>{` button { padding: ${"large" in props ? "50" : "20"}px; background: ${props.theme.background}; } `}</style> </button> );
|
CSS Modules
CSS Modules 是用來解決 CSS 重複命名需要時常處理衝突的問題,在 Create React App 中 CSS 命名規則為 {檔名}.module.css
。
main.module.css
1 2 3
| .information { color: red; }
|
page.module.css
1 2 3
| .information { color: green; }
|
1 2 3 4 5 6 7 8 9 10 11
| import styleMain from "./main.module.css"; import stylePage from "./page.module.css";
export default function Test() { return ( <div> <h1 className={styleMain.information}>App</h1> <h3 className={stylePage.information}>Test</h1> </div> ); }
|
在 Class 的命名用駝峰式會較佳,但沒有一定。
1 2
| style["class-name"]; style.className;
|
StyleX
Facebook 推出的 Atomic CSS in JS 解決方案 StyleX 上線啦!
透過 Build Time 就處理好的樣式效能海放 Run Time 的 Styled Component 同時也減少了要背一堆框架內建的 Util CSS 語法值得肯定。
API 也非常單純主要就是兩個
- stylex.create: 產生樣式
- stylex.props: 把樣式套用到元件
底下是官方最基本的範例:
1 2 3 4
| const styles = stylex.create({ red: { color: "red" }, }); let a = stylex.props(styles.red);
|
經過編譯後,會自動產生出來一個動態的 class 以及對應的樣式,完整的解決了命名問題並同時提供原子化樣式的優點,大量的降低網站樣式的流量。
1
| let a = { className: "x1e2nbdu" };
|
1 2 3
| .x1e2nbdu { color: red; }
|
雖然是 facebook 推出的但由於主要是透過兩個 function 來產生出樣式,所以可以直接套用在其他的框架也是沒有問題。
喜歡這篇文章,請幫忙拍拍手喔 🤣