iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0
Modern Web

react 學習記錄系列 第 24

[Day24]我的 react 學習記錄 - emotion

  • 分享至 

  • xImage
  •  

這篇文章的主要內容

簡單介紹 react 裡面 style 的工具 emotion。


Emotion

目前為止都盡量不動到 style 即使有用到也是使用 inline 或是 class 的方式來確保畫面上出現比較少的 code 以方便閱讀為主,今天來跟大家分享在 react 裡面常用到的 css-in-js Library - Emotion。

Install

npm install @emotion/styled @emotion/react
or
yarn add @emotion/styled @emotion/react


css 語法

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;

syntax

  • css={css ``{css syntax}``}
    or
  • css={function}

透過 css 語法我們可以在 `` 裡面撰寫有點像是 SCSS 的語法來 style 我們的元素,另外如果有需要傳遞 props 的情況會需要在要放入變數的地方加上 ${} 才會進行處理。

看起來雖然有點像是寫 inline style 的語法,但是不一樣,inline style 是回傳一個 camelCase 的 style 物件,在 css 語法裡面寫的真的就是一般的 css。

另外如果你跟我一樣是使用 vite 進行打包,請記得在檔案的最上方加上 /** @jsxImportSource @emotion/react */ 這一行告訴 vite,這份檔案有使用 emotion 進行打包要另外處理,這樣我們的 css 語法才會起作用。

emotion

可以看到當狀態改變時我的 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 語法可以讓我們建立一個具備 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;

syntax

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 */

emotion-2

結果相同。


繼承

另外 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 來分別兩個不同的元件。

emotion-3


as

另外 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 的文件網站。

emotion-4

可以從開發者工具看到我的 div 確實被改變成了 a 標籤,並且點擊也真的出現了網頁跳轉的效果,但是因為 a 標籤預設 style 的問題,UI 的呈現上出現了一些不同的狀況。

我個人在開發時會避免使用 as 標籤來修改元件的標籤,因為每個標籤都會有自己預設的 style,如果透過 as 來修改標籤,有可能導致設定的 style 不起作用。

另外相似的工具還有 styled-component,語法跟用法非常接近,但是 emotion 在 package 的 size 上比 styled-component 來的要小一點。


emotion document

下一篇簡單介紹全域管理工具 Zustand
如果內容有誤再麻煩大家指教,我會盡快修改。

這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium


上一篇
[Day23]我的 react 學習記錄 - react router
下一篇
[Day25]我的 react 學習記錄 - Zustand
系列文
react 學習記錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言