CSS in JS (react.js) 簡介與優缺點分析

me
林彥成
2019-08-29 | 2 min.
文章目錄
  1. 1. 問題定義
  2. 2. 優點與解決的問題
  3. 3. 缺點
  4. 4. styled-components
    1. 4.1. 套用主視覺配置
    2. 4.2. RWD
  5. 5. styled-jsx

近幾年,由於 component-based 的概念興起,元件開發成為顯學,css-in-js 不僅降低了維護難度也加速了開發速度,常見的 library 像是 styled-components 或是 Next.js 的 styled-jsx 都非常好上手。

問題定義

當元件需要在其他專案使用時,在搬動上會增加許多不必要的困難,像是除了必須搬完相對應的樣式檔,然後還必須放在正確的位置中。

當我們用了其他的函式庫,像是在 Next.js 的專案最佳化時,樣式檔的引用順序就必須讓機器不會 confuse,但若是我們將樣式檔拆的太細,很可能就會發生在 code splitting 時機器無法判斷順序的問題。

優點與解決的問題

CSS in JS 是減少麻煩的一個好概念,直接把該寫的樣式一起寫在元件中,有使用過也蠻推薦的像是 styled-components、styled-jsx ( Next.js 內建並維護,較適合有伺服器渲染的專案)。

  • 解決 code spliting 可能出現的順序問題
  • 解決命名互相覆蓋,在撰寫樣式檔時也可以用 BEM 的命名規則避開或導入原子化樣式設計
  • 維護時不需要再去找到底被哪個樣式檔影響
  • 自動幫樣式加上瀏覽器的前綴,樣式檔就要再加上 postCSS 幫忙處理

缺點

  • 切換主視覺要多用一個 <ThemeProvider>,傳入的 Props 差異太大時有點麻煩
  • RWD 不像 bootstrap 有格線系統那樣使用方便,建議也可以只導入格線系統混合使用
  • 會比較難做效果跟做覆蓋,不同狀態需要不同樣式時要再封裝一層
  • 語法的高亮、自動完成、防呆較不完整
  • 少了可以共用樣式的好處,像是 @extend 或 mixin 這樣的寫法,需把元件切的夠小,才會有一定程度的共用性

styled-components

styled-components 提供了在 JavaScript 中直接撰寫 CSS 的介面,意味著可以使用所有的 CSS 功能。

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 在外層注入就可以在元件中直接取值。

  1. 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>
);
}
}
  1. 在元件中直接取值,甚至可以寫 Function 進行判斷,做出更多元的變化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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
28
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>
);

喜歡這篇文章,請幫忙拍拍手喔 🤣

share