簡單介紹 react 裡面 style 的工具 emotion。
目前為止都盡量不動到 style 即使有用到也是使用 inline 或是 class 的方式來確保畫面上出現比較少的 code 以方便閱讀為主,今天來跟大家分享在 react 裡面常用到的 css-in-js Library - Emotion。
npm install @emotion/styled @emotion/react
oryarn add @emotion/styled @emotion/react
emotion 有兩種比較主要在 react 裡面撰寫 style 的方法。
第一個是 css
的語法,先從 "@emotion/react”
裡面引入 css
function。
使用 emotion 可以在 JSX 標籤上額外新增一個 css 屬性,可以接收 css
字串或是 function。
/** @jsxImportSource @emotion/react */
import { useState } from "react";
import { css } from "@emotion/react";
function App() {
const [isLight, setIsLight] = useState(false);
function handleLight() {
setIsLight(!isLight);
}
return (
<div>
<div
css={css`
padding: 32px;
background-color: ${isLight ? "#ccc" : "#999"};
color: ${isLight ? "#000" : "#fff"};
font-size: 24px;
border-radius: 4px;
&:hover {
background-color: #a00;
}
`}
>
Click button or hover to change color.
</div>
<button onClick={handleLight}>Toggle Light</button>
</div>
);
}
export default App;
css={css ``{css syntax}``}
css={function}
透過 css
語法我們可以在 ``
裡面撰寫有點像是 SCSS 的語法來 style 我們的元素,另外如果有需要傳遞 props 的情況會需要在要放入變數的地方加上 ${}
才會進行處理。
看起來雖然有點像是寫 inline style 的語法,但是不一樣,inline style 是回傳一個 camelCase 的 style 物件,在 css
語法裡面寫的真的就是一般的 css。
另外如果你跟我一樣是使用 vite 進行打包,請記得在檔案的最上方加上 /** @jsxImportSource @emotion/react */
這一行告訴 vite,這份檔案有使用 emotion 進行打包要另外處理,這樣我們的 css 語法才會起作用。
可以看到當狀態改變時我的 calss 也會隨之該變,那是因為這一類的 css-in-js 的 Library,都是透過 JavaScript 來產生 class 並幫我們主動掛上,而且會透過產生隨機的 class 名稱來避免元素出現 class 撞名污染的問題。
但是這樣寫跟我們寫 inline 沒有什麼太大的差別,在閱讀上沒有比較好讀,剛剛有提到我們 css 屬性也可以接收一個 function,這樣就可以透過 function 回傳 css
字串來把 style 拆開來處裡。
const style = (isLight: boolean) => {
return css`
padding: 32px;
background-color: ${isLight ? "#ccc" : "#999"};
color: ${isLight ? "#000" : "#fff"};
font-size: 24px;
border-radius: 4px;
&:hover {
background-color: #a00;
}
`;
};
function App() {
const [isLight, setIsLight] = useState(false);
function handleLight() {
setIsLight(!isLight);
}
return (
<div>
<div css={() => style(isLight)}>
Click button or hover to change color.
</div>
<button onClick={handleLight}>Toggle Light</button>
</div>
);
}
這樣就可以維持元件內的整潔,把 css 一到元件外部來處理了,因為是一個 function,所以也可以把 state 傳遞到 function 裡面。
接著是我個人比較常使用的 styled 語法,使用 styled 語法可以讓我們建立一個具備 styled 的 react 元件。
一樣從 @emotion/styled
引入 styled。
import { useState } from "react";
import styled from "@emotion/styled";
type props = {
isLight: boolean;
};
const Div = styled.div<props>`
padding: 32px;
background-color: ${({ isLight }) => (isLight ? "#ccc" : "#999")};
color: ${({ isLight }) => (isLight ? "#000" : "#fff")};
font-size: 24px;
border-radius: 4px;
&:hover {
background-color: #a00;
}
`;
function App() {
const [isLight, setIsLight] = useState(false);
function handleLight() {
setIsLight(!isLight);
}
return (
<div>
<Div isLight={isLight}>Click button or hover to change color.</Div>
<button onClick={handleLight}>Toggle Light</button>
</div>
);
}
export default App;
const SomeComponent = styled.{htmlElement}``{css syntax}``
SomeComponent
: 透過 styled 語法建立的 styled component 命名原則跟 react component 一樣必須維持 CamelCase 的寫法。
htmlElement
: 你希望建立的 html 標籤。
既然建立的是 react 元件,一樣可以傳入到 props 到元件內,在 styled component 傳入的 props,可以透過在 ${}
裡面放入一個 function 來取得傳下去的 props。
上面切換成 styled 的語法結果會跟 css 的結果相同,但是使用 styled 語法就可以把這個註解移除 /** @jsxImportSource @emotion/react */
。
結果相同。
另外 styled 還有繼承的寫法,如果有相同的 styled 大部分相同只有調整一小部分的話可以透過繼承來共用 style。
import { useState } from "react";
import styled from "@emotion/styled";
type props = {
isLight: boolean;
};
const Div = styled.div<props>`
padding: 32px;
background-color: ${({ isLight }) => (isLight ? "#ccc" : "#999")};
color: ${({ isLight }) => (isLight ? "#000" : "#fff")};
font-size: 24px;
border-radius: 4px;
&:hover {
background-color: #a00;
}
`;
const DivTwo = styled(Div)`
&:hover {
background-color: #0a0;
}
`;
function App() {
const [isLight, setIsLight] = useState(false);
function handleLight() {
setIsLight(!isLight);
}
return (
<div>
<Div isLight={isLight}>Click button or hover to change color.</Div>
<DivTwo isLight={!isLight}> Here is DivTwo component.</DivTwo>
<button onClick={handleLight}>Toggle Light</button>
</div>
);
}
這邊我透過 styled(Div)
來讓 DivTwo
繼承所有 Div
的 style 並且修改 hover 時的背景色。
另外我傳入了不同的 props 來分別兩個不同的元件。
另外 styled component 所產生出來的 react 可以接收到一個特別的 props as
透過 as 可以修改元件的 html 標籤。
function App() {
const [isLight, setIsLight] = useState(false);
function handleLight() {
setIsLight(!isLight);
}
return (
<div>
<Div isLight={isLight}>Click button or hover to change color.</Div>
<DivTwo
as="a"
href="https://emotion.sh/docs/introduction"
isLight={!isLight}
>
Here is DivTwo component.
</DivTwo>
<button onClick={handleLight}>Toggle Light</button>
</div>
);
}
我透過 as 屬性把我的 DivTwo 轉變成了 a 標籤並且給他 href 跳轉到 emotion 的文件網站。
可以從開發者工具看到我的 div 確實被改變成了 a 標籤,並且點擊也真的出現了網頁跳轉的效果,但是因為 a 標籤預設 style 的問題,UI 的呈現上出現了一些不同的狀況。
我個人在開發時會避免使用 as 標籤來修改元件的標籤,因為每個標籤都會有自己預設的 style,如果透過 as 來修改標籤,有可能導致設定的 style 不起作用。
另外相似的工具還有 styled-component,語法跟用法非常接近,但是 emotion 在 package 的 size 上比 styled-component 來的要小一點。
下一篇簡單介紹全域管理工具 Zustand
如果內容有誤再麻煩大家指教,我會盡快修改。
這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium