CSS模組化與CSS-in-JS是近年非常熱議且時常被討論的話題,最近特別是emotion被很多人推崇與討論,這邊將會跟大家介紹他主要的特色有哪些。

styled模組化

官方有留下一段話:
styled was heavily inspired by styled-components and glamorous

使用方式其實跟styled component幾乎一樣的,模組化的特性剛好也很適合配合React的元件化設計
並且可以將其他模組化的元件擴充style,這樣的設計是彈性的。

1
2
3
4
5
6
7
8
9
10
11
12
13
import styled from '@emotion/styled'

const ButtonWrapper = styled.button`
color: turquoise;
`;

const Button = ({ text }) => {
return (
<ButtonWrapper>
{text}
</ButtonWrapper>
);
};

當然也是可以傳遞參數過去的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import styled from '@emotion/styled'

const ButtonWrapper = styled.button`
color: turquoise;
font-size: ${props => props.fontSize};
`;

const Button = ({ text }) => {
return (
<ButtonWrapper fontSize={16}>
{text}
</ButtonWrapper>
);
};

還有搭配CSS Object的寫法,這個我就放到下面補充。

CSS Object

emotion的特色之一就是可以攥寫好的Object放入element裏面使用。
如果你要使用他們提供的JSX,需要注意一些設定:

  1. Babel Preset
  2. JSX Pragma

主要是讓你在編譯後的程式為jsx而不是React.createElement

這樣就可以使用了

1
2
/** @jsx jsx */
import { jsx } from '@emotion/react';

如官方的範例所示,用法跟inline-style的寫法很像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** @jsx jsx */
import { jsx } from '@emotion/react'

render(
<div
css={{
backgroundColor: 'hotpink',
'&:hover': {
color: 'lightgreen'
}
}}
>
This has a hotpink background.
</div>
)

CSS + Styled

另外剛剛提到的例子可以搭配CSS Object,使用到props的部分一次更新,避免重複多次撰寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import styled from '@emotion/styled'

const Button = styled.button(
{
color: turquoise;
},
props => ({
fontSize: props.fontSize
})
);

const Button = ({ text }) => {
return (
<ButtonWrapper fontSize={16}>
{text}
</ButtonWrapper>
);
};

CSS Selectors

當然也可以搭配使用class selectors來給予child樣式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* @jsx jsx */
import { jsx } from '@emotion/react'

render(
<div
css={{
color: 'darkorchid',
'& .name': {
color: 'orange'
}
}}
>
This is darkorchid.
<div className="name">This is orange</div>
</div>
)

CSS Array

裡面除了可以放CSS Object,也可以改放Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** @jsx jsx */
import { jsx } from '@emotion/react'

render(
<div
css={[
{ color: 'darkorchid' },
{ backgroundColor: 'hotpink' },
{ padding: 8 }
]}
>
This is darkorchid with a hotpink background and 8px of
padding.
</div>
)

Fallback支援

透過定義fallback可以避免瀏覽器不支援部分的語法,讓你擁有備援功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @jsx jsx */
import { jsx } from '@emotion/react'

render(
<div
css={{
background: [
'red',
'linear-gradient(#e66465, #9198e5)'
],
height: 100
}}
>
This has a gradient background in browsers that support
gradients and is red in browsers that don't support
gradients
</div>
)

CSS Composition

emotion強大的地方就是,你可以在css裏面再組裝另外的css。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** @jsx jsx */
import { jsx, css } from '@emotion/react'

const base = css`
color: hotpink;
`

render(
<div
css={css`
${base};
background-color: #eee;
`}
>
This is hotpink.
</div>
)

看到這裡你是否會產生一個疑問:不過就是把CSS拆得更細而已嗎?這樣不會把整個架構變的更複雜嗎?
不會,因為他解決了一些開發上面臨的問題。
官方表示:
With regular css, you can compose styles together using multiple class names, but this is very limited because the order that they’re defined is the order they’ll be applied. This can lead to hacks with !important and such to apply the correct styles.
For example, we have some base styles and a danger style, we want the danger styles to have precedence over the base styles but because base is in the stylesheet after danger it has higher specificity. In regular CSS, you might do something to make danger have a higher specificity than base like moving the danger class, so it’s more specific than base, use !important or abandon composition and rewrite the styles each time you need them.

如果今天有一個Button具有primary和danger的樣式,今天我開發的時候突然想要換成danger的樣式,可是primary在danger的後面,所以每次編譯的時候都會把樣式蓋過去,以往的做法是會用一些方法把權重提高,例如:important。
這個範例是會無效的,danger被base蓋過去了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
render(
<div>
<style>
{`
.danger {
color: red;
}
.base {
background-color: lightgray;
color: turquoise;
}
`}
>
</style>
<p className="base danger">What color will this be?</p>
</div>
)

透過composition的設計,你可以輕易的調整先後順序來控制權重
這個設計是不是很方便呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** @jsx jsx */
import { css, jsx } from '@emotion/react'

const danger = css`
color: red;
`

const base = css`
background-color: darkgreen;
color: turquoise;
`

render(
<div>
<div css={base}>This will be turquoise</div>
<div css={[danger, base]}>
This will be also be turquoise since the base styles
overwrite the danger styles.
</div>
<div css={[base, danger]}>This will be red</div>
</div>
)

Global style

這個對一開始設置專案的時候其實非常實用,特別是我們要設置CSS Reset的時候我們可能會寫在一個CSS file裏面再從App import,對於有強迫症的人來說,這樣可能是一個很奇怪的寫法(因為混用)所以可以這樣使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Global, css } from '@emotion/react'

render(
<div>
<Global
styles={css`
.some-class {
color: hotpink !important;
}
`}
/>
<Global
styles={{
'.some-class': {
fontSize: 50,
textAlign: 'center'
}
}}
/>
<div className="some-class">This is hotpink now!</div>
</div>
)

以這個例子來說,你可以想像成是將兩個style變成global使用。
我是習慣使用styled搭配Global:

1
2
3
4
5
6
7
8
9
10
const Reset = ({ children }) => {
return (
<BodyWrapper>
<Global styles={ResetCSS} />
{children}
</BodyWrapper>
);
};

export default Reset;

為何不使用styled-component

兩者皆具有CSS模組化的功能,這邊有人分享關於兩者的比較
可以看到兩者的整體表現其實是差不多的,但是emotion在整體表現上有些微的比styled component好一些
emotion能操作的手法也比styled component多一些,所以以2021年的趨勢來說,emotion的討論度是很高的
我最近寫的小專案也是使用emotion而非styled component
對我來說,他的靈活度與彈性是稍微好一點的

結論

我個人是蠻喜歡使用emotion撰寫CSS-in-JS的,最近寫的專案不管是用React或是開發Nextjs我也都會使用emotion,協作對象是剛接觸React的新手我覺得也是挺合適的,至少不會遇到以前撰寫樣式容易遇到蓋來蓋去衍生出的各種問題。