Skip to main content

[React] Styled-component

在使用 React 框架進行開發時,大致會有 3 種使用 CSS 檔案的方法

  1. 匯入 CSS 檔案
import "./example.css";
  1. inline-style
return <button style={{ backgroundColor: "red", fontSize: "24px" }}></button>;
  1. css in js

    現在主流的工具有styled-components@emotion/css

這篇文章主要介紹 css in js 的工具之一: styled-components

安裝與匯入

npm i styled-components

原本假設已經有寫好的 scss 檔案,我們可以直接把副檔名改成.jsx,然後再去判斷哪些 class 需要組件化。(這邊說明直接使用 scss 檔案是因為 styled-components 可以直接支援。)

一開始還是需要引入套件

import styled from "styled-components";

基本範例

我們以 styled-components 的官網首頁為例,可以看到白色的 GitHub 按鈕

這個按鈕的程式碼如下:

const Button = styled.a`
/* This renders the buttons above... Edit me! */
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
border: 2px solid white;

/* The GitHub button is a primary button
* edit this to target it specifically! */
${(props) =>
props.primary &&
css`
background: white;
color: black;
`}
`;

製作出個 Button 的元件,在styled之後可以插入不同的 HTML 元素,這邊為 a 元素,用反引號(backticks)將該元件的 SCSS 放入其中。因為是.jsx 檔,我們可以帶入上層元件的props和使用 JavaScript 進行操作。

包裹元件

我以之前練習的按鈕元件作為範例,要將.scss 檔改為.jsx,並置換內容為 styled-components,可以參考button.styles.jsx的變換。

按鈕分成一般按鈕、Google 登入按鈕、反置按鈕,他們的共同處是都是一般按鈕的延伸,故我們可以先建立一般按鈕,在透過style()包裹住一般按鈕來新增屬性

export const BaseButton = styled.button`
// some css
`;

export const GoogleSignInButton = styled(BaseButton)`
// google button specific css
`;

export const InvertedButton = styled(BaseButton)`
// inverted button specific css
`;

通用的按鈕

可以參考按鈕元件裡面的button.component.jsx,我們的目標是要回傳一個通用的按鈕,他會依據 props 的不同,回傳對應的按紐。

首先要引入三種按鈕,我們建立三種按鈕的類型分別為 base, google, inverted,然後建立一個getButton的函式會回傳我們需要的按鈕元件

import {
BaseButton,
GoogleSignInButton,
InvertedButton,
} from "./button.styles";

export const BUTTON_TYPE_CLASSES = {
base: "base",
google: "google-sign-in",
inverted: "inverted",
}; // 外部引入的元件可以傳入需要的按鈕類型,ex: buttonType={BUTTON_TYPE_CLASSES.inverted}

const getButton = (buttonType = BUTTON_TYPE_CLASSES.base) =>
({
[BUTTON_TYPE_CLASSES.base]: BaseButton,
[BUTTON_TYPE_CLASSES.google]: GoogleSignInButton,
[BUTTON_TYPE_CLASSES.inverted]: InvertedButton,
}[buttonType]);

透過 getButton 函式來取得通用按鈕再回傳

const Button = ({ children, buttonType, ...otherProps }) => {
const CustomButton = getButton(buttonType);
return <CustomButton {...otherProps}>{children}</CustomButton>;
};

通用 google 按鈕使用範例: sign-in-form.component.jsx

<Button
type="button"
buttonType={BUTTON_TYPE_CLASSES.google}
onClick={signInWithGoogle}
children={"Google sign in"}
></Button>

引用其他 styled-components

可以參考購物車元件裡面的cart-dropdown.styles.jsx,匯出了三種按鈕,並被引用在新建立的 styled-component 之中

import {
BaseButton,
GoogleSignInButton,
InvertedButton,
} from "../button/button.styles";

export const CartDropdownContainer = styled.div`
// ... other scss

${BaseButton},
${GoogleSignInButton},
${InvertedButton} {
margin-top: auto;
}
`;

SVG 元素

參考購物車圖案裡面的cart-icon.styles.jsx,可以使用 SVG 的 style-component 的方法如下

import { ReactComponent as ShoppingSvg } from "../../assets/shopping-bag.svg";

export const ShoppingIcon = styled(ShoppingSvg)`
width: 24px;
height: 24px;
`;

Props 改變屬性

參考引導商品裡面的directory-item.styles.jsx,每個 BackgroundImage 的圖片會由給入的 props 決定

export const BackgroundImage = styled.div`
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-image: ${({ imageUrl }) => `url(${imageUrl})`};
`;

css in js 全域變數

參考表格輸入裡面的form-input.styles.jsx,透過 const 建立變數,並使用css函式來去儲存固定的 css 給其他元件使用

import styled, { css } from "styled-components";

const subColor = "grey";
const mainColor = "black";

const shrinkLabelStyles = css`
top: -14px;
font-size: 12px;
color: ${mainColor};
`;

export const FormInputLabel = styled.label`
color: ${subColor};
font-size: 16px;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 5px;
top: 10px;
transition: 300ms ease all;

${({ shrink }) =>
shrink &&
shrinkLabelStyles}// 常見的jsx寫法,shrink為真才會帶入shrinkLabelStyles
`;

匯入 FormInputLabel 如下,參考form-input.component.jsx

import { FormInputLabel, Input, Group } from "./form-input.styles";

...
<FormInputLabel shrink={otherProps.value.length}>
{label}
</FormInputLabel>
...

結論

css in js 的寫法主要可以透過 styled-components 的套件去撰寫,以前在檔案繁多時,常會有重複 class 命名的衝突問題,造成屬性重疊、畫面不如預期,透過此套件可以成功地的克服。由於此套件會隨機產生 class 的名稱,有時會造成溯源不易,我們可以參考莫大的文章,將 className 作為 props 帶入 styled-component,來達到指定的 class 名稱。如此一來,我們就能透過 js 來撰寫 css 了!

參考文章