4-3 用 JavaScript 寫 CSS!? CSS in JS 的使用

本單元對應的專案分支為:install-emotion

單元核心#

這個單元的主要目標包含:

  • 了解 CSS in JS 的概念
  • 下載 emotion
  • 使用 emotion 提供的 styled components 來建立元件

傳統的網頁開發上,我們會把所有的 CSS 樣式寫在一支或多支的 CSS 檔內,接著在 index.html 中透過 <link rel="stylesheet" href="main.css" /> 的方式讓整個網站都能夠套用到這支 CSS 所撰寫的樣式。

上述這種方式因為所有的樣式都是作用在整個網頁的環境下,常會發生不小心命名了同樣的 class 名稱,導致樣式相互影響或彼此覆蓋,又或者發生某些樣式權重不夠的情況而難以調整,因此在 class 的命名上常常需要非常留意小心,進而也出現了許多對於 class 命名的不同規範和設計模式。

在這個單元中將會說明為什麼需要把 CSS 寫在 JS 檔案中,這麼做有什麼好處?並且實際透過 emotion 這個套件來實作。

為什麼要把 CSS 寫在 JS 中(CSS-in-JS)#

現今前端框架中,因為可以把各個元件給拆分開來,每個不同功能的按鈕都可以是不同的元件,每個元件之間可以是獨立不互相干擾的,所以連 CSS 的樣式也都可以有元件的概念存在,也就是說,在某個元件內所撰寫的樣式,即使有相同 class 的命名,但在最後編譯後這些樣式都只會作用在該元件內,不會干擾到外層或其他元件的樣式

這類把樣式連同元件寫在一起寫法稱作 CSS-in-JS,它的好處在於每個元件都是獨立可被重複使用,你不用再擔心改了 A 卡片的樣式卻不小心連 B 卡片的顏色也變了;也不用再擔心以為某支 CSS 檔案是多餘的,砍掉之後卻發生破版的情況,因為現在元件和樣式是綁在一起的,只要這個元件是完整的,那麼放到另一個地方去使用它時,外觀和功能也會是一樣的。

除此之外,既然 CSS 已經被放入的 JS 檔案中,如同把 HTML 寫在 JS 檔中的 JSX 一樣,這些 CSS 的樣式也將適用 JS 的語法

info

透過把 CSS 寫在 JS 中的這種寫法,可以確保特定樣式只會作用在該元件,同時可以把 JS 中的一些邏輯判斷放到 CSS 使用。

現在,就讓我們來看看怎麼使用帶有樣式的元件吧!

提醒

實務上會同時搭配上述兩種做法,有些樣式仍會撰寫在全域環境,讓整個網頁都可以套用到該樣式(例如版型、主題、字體 ...... 等);針對個別元件則會撰寫只用在該元件內的樣式。

CSS-in-JS 套件的選擇: emotion#

React 中要讓每個元件帶有獨立樣式的做法很多,這裡我們會以 emotion 這個非常多人使用的套件來做說明。

imgur

修改全域用的 CSS#

在實際開始使用 emotion 前,讓我們先針對會影響到所有頁面的 CSS 來進行調整,這裡將會分成兩個部分。

安裝 normalize.css#

由於不同瀏覽器預設的樣式會有些不同(例如,在 Chrome、Firefox、或 Safari 等不同瀏覽器中,預設的行高不同),因此在開始撰寫 CSS 前,為了讓各瀏覽器的基本樣式一致,開發者常會先定義一些樣式,目的是把瀏覽器預設的樣式加以覆蓋和重設,讓各瀏覽器的表現盡量一致,而 normalize.css 就是由眾多開發者共同整理讓瀏覽器能一致的樣式。

安裝的方式一樣會透過 npm,在終端機中輸入:

$ npm install --save normalize.css

接著打開 src/index.js,在最上方的地方透過 importnormalize.css 載入:

// ./src/index.js
// ...
import 'normalize.css';
ReactDOM.render(
/*...*/,
document.getElementById('root')
);

修改 index.css#

接著我們要來針對全域環境下(也就是會影響到整個網站)的 CSS 樣式進行調整,先打開 src/index.css 這支檔案,在檔案的最上面加入以下的 CSS 語法,目的是要讓我們後續完成的元件可以撐滿整個畫面:

/* ./src/index.css */
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
#root {
height: 100%;
width: 100%;
}
補充

在原本的 src/index.css 中,預設 React 針對字體和 <code> 區塊有做了一些樣式的設定,這個部分對於「臺灣好天氣」的專案開發沒有任合影響,讀者可以選擇把這個預設的樣式移除。

在沒有 emotion 以前:定義 CSS 樣式並使用 className#

修改完了全域的 CSS 樣式後,現在我們來修改 App 元件的樣式。

如同前幾個章節的做法,在沒有 emotion 之前,過去我們會透過定義 CSS Class 的方式,並將這些 Class 套用在元件的 className 屬性中。現在我們先照著過去的方法實作一次。

先在 ./src/App.css 的地方去修改 CSS 樣式,把原本的樣式都移除,建立 .container.weather-card 這兩個 class:

/* ./src/App.css */
.container {
background-color: #ededed;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.weather-card {
min-width: 360px;
box-shadow: 0 1px 3px 0 #999999;
background-color: #f9f9f9;
}

接著打開 ./src/App.js,把 create-react-app 預設幫我們產生的內容移,改成簡單的一個標題,並且套用剛剛寫好的兩個 CSS Class,分別是 containerweather-card

// ./src/App.js
import React from 'react';
import './App.css';
function App() {
return (
<div className="container">
<div className="weather-card">
<h1>Weather</h1>
</div>
</div>
);
}
export default App;

現在透過 npm start 會自動幫我們啟動開發用的伺服器,接著進到 http://localhost:3000/ 就可以看到已經改成對應的畫面:

Imgur

改成使用 emotion 的 Styled Components#

現在當我們要撰寫 CSS-in-js 的寫法時,就不用再需要使用 className 的方式來套用樣式,也不需要再載入額外的 ./src/App.css ,而是會把樣式和元件結合在一起。

安裝 emotion#

這裡我們一樣會使用 Node.js 的套件管理工具(npm)來安裝 emotion。先使用 VSCode 打開專案資料夾,並於終端機輸入:

$ npm install @emotion/react @emotion/styled

安裝好了之後,你應該會發現在 package.json 的檔案中多了 @emotion/core 的部分:

Imgur

補充

package.json 中會記錄所有在這個專案有使用到的 JavaScript 套件,未來如果自己或他人需要重新啟動這個專案的時候,npm 就會自動去檢視 package.json 中的紀錄,幫開發者安裝回相對應的套件。

載入 emotion 套件#

接著把原本載入的 ./src/App.css 移除,改成透過 import 載入 @emotion/styled 這個套件:

// ./src/App.js
import React from 'react';
import styled from '@emotion/styled'; // STEP 1:載入 emotion 的 styled 套件
// ...

撰寫 styled components#

接著把原本定義在 App.css 中 CSS 樣式的內容,改成帶有樣式的元件,這種帶有樣式的元件稱作 styled component,這裡分別建立兩個名稱為 containerweatherCard 的 Styled Component。

提示

Styled Components 除了指的是帶有樣式的 React 元件外,也是一個套件的名稱。若你有過 React 的開發經驗,便可能聽過 Styled Components 這個套件,這個套件在 React 生態系中也非常多人使用,而 emotion 則是受 Styled Components 這個套件的啟發,將這樣的功能融入自己原本的套件功能中中供開發者使用。

實際上,以筆者自己使用上的經驗,這兩個套件在 Styled Components 上的用法非常接近,學會其中一套後,在接觸另一套時,幾乎可以無痛轉移。

我們先把 container 改成元件的寫法:

// ./src/App.js
import styled from '@emotion/styled';
// STEP 2:定義帶有 styled 的 component
const Container = styled.div`
background-color: #ededed;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
  • 第一次看到這種寫法,相信你會覺得非常神秘且不習慣,要建立一個帶有樣式的 <div> 標籤時,只需要使用 styled.div;如果要建立的是 <button> 則是使用 styled.button 其他則以此類推。
  • 接著在 styled.div 後面加上兩個反引號(和 Template Literals 用的符號相同),在兩個反引號之間就可以直接撰寫 CSS 。實際上這裡的 styled.div 是一個函式,而在函式後面直接加上反引號一樣屬於 Template Literals 的一種用法,只是比較少情況會這樣使用。

接著把 weather-card 也改成元件:

const WeatherCard = styled.div`
position: relative;
min-width: 360px;
box-shadow: 0 1px 3px 0 #999999;
background-color: #f9f9f9;
box-sizing: border-box;
padding: 30px 15px;
`;

這裡你可以特別留意到,React 元件都會以大寫駝峰來做命名,因此 container 或變成 Containerweather-card 變成 WeatherCard。另外原本的 CSS 樣式則會使用「樣板字面值」的反引號(數字 1 左邊的符號)包住。

小提醒

若想進一步了解在函式後面直接加上反引號的這種 Template Literal 用法,可於本單元 Github 上的說明頁面查看 JavaScript Template Literals and styled-components 這篇文章。

使用撰寫好的 styled component#

剛剛我們寫好的 styled components 本質上就是 React 元件了!有沒有覺得很神奇!所以我們可以直接把它放到 JSX 中使用:

// ./src/App.js
// ...
// STEP 3:把上面定義好的 styled-component 當成元件使用
const App = () => {
return (
<Container>
<WeatherCard>
<h1>Weather</h1>
</WeatherCard>
</Container>
);
};

現在可以再次使用 npm start 把專案啟動起來,你會看到和剛剛直接使用 className 的方式有一樣的畫面。

透過瀏覽器的開發者工具可以看到,這些帶有樣式的元件,最後都會帶上特殊的 class 名稱,並且套用上剛剛所撰寫的 CSS 樣式,而這也就是為什麼不同元件之間的 CSS 樣式不會相互干擾的原因。即使在不同元件中都定義了一個同樣名為 <Container /> 的 styled-component,但因為它們最終會帶上不同的 class 名稱,因此元件間的樣式並不會相互干擾:

Imgur

換你了!安裝並使用 emotion#

現在換你實際來建立看看 Styled Components 吧!你可以參考下面的步驟加以完成:

  • 安裝 emotion 相關套件,包含 @emotion/core@emotion/styled
  • 使用 emotion 提供的 styled 方法來建立 styled components,分別是 ContainerWeatherCard
  • 在 JSX 中套用建立好的 styled component
  • 移除專案中用不到的檔案(App.css, App.test.js, logo.svg

完成後,你可以到專案的 Github 網址,切換到名為 install-emotion 的分支即可檢視本專案的程式碼:

Imgur

這裡再稍微提醒一下,如果你想要看這個單元的程式碼有哪些變更,可以點選右方的「時鐘」圖示:

Imgur

選擇其中一個 commit 後:

Imgur

即可檢視本單元程式碼有變更的部分(一般筆者會放在最上面一個 commit):

Imgur

在後面幾個單元中,讀者也都可以循著這樣的方式檢視各單元完整的程式碼以及該單元程式碼有變更的部分。


本單元使用到的連結、完整程式碼與變更部分(時鐘圖示)可於 install-emotion 分支檢視:https://github.com/pjchender/learn-react-from-hook-realtime-weather-app/tree/install-emotion

Imgur