0%

[week 21] React 基礎:style & 如何撰寫 CSS

本篇為 [FE302] React 基礎 - hooks 版本 這門課程的學習筆記。如有錯誤歡迎指正!


React 中的 style

在 React 中有很多種方式可以寫 CSS,主要又可分為下列三種:

一、inline-style 行內樣式

直接在 HTML 標籤內加入 style 屬性,例如 style={},但需注意下列幾點:

  • inline style 裡面放的是 object
  • 只能傳入該元素支援的 inline style
  • 而不能傳入偽元素或 hover
  • 因為是 JavaScript 程式碼,需改為駝峰式命名
function Title({ size }) {
  if (size === 'XL') {
    return <h1>hello!</h1>
  }
  return(
    // 兩個大括號包住: JavaScript 程式碼 + 以物件形式
    <h2 style={{
      color: 'blue',
      // 需改為駝峰式命名
      textAlign: 'center'
    }}>hello!</h2>
  )
}

顯示結果:

二、使用 webpack 打包

在標籤加上 className 屬性(因為 class 是保留字),會被瀏覽器渲染成 css 中的 class,再透過 webpack 打包來引入該 css 檔案:

// 透過 webpack 來引入 css
import './App.css';

function App() {
  return (
    <div className="App">
      <Title />
    </div>
  );
}

然後編輯 App.css 檔案:

.App {
  text-align: center;
  background: yellowgreen;
}

顯示結果:

三、使用 styled-components 套件

styled-components 是一個 library,也是目前的主流作法。用了 styled-components 套件之後,基本上就不需再直接寫 App.css 等檔案,之後會以此方法進行介紹。

首先安裝 styled-components 套件,然後運行程式:

$ npm install --save styled-components
$ npm run start

css 是透過標籤模板的寫法,在也就是在 “`” 反引號裡面寫入 css 樣式:

style.p`<css code>`

修改 App.js 檔案,直接在 style 後面接元素名稱,並在反引號中寫入 css 程式碼:

// 引入 styled-components 套件
import styled from 'styled-components';
 
const Description = styled.p`
  color: red;
  padding: 20px;
  bottom: 1px solid #000;
`

function App() {
  return (
    <div className="App">
      <Title />
      <Description>  // => 編譯後會變成 <p>
        這是副標題
      </Description>
    </div>
  );
}

可以想成 Description 就是有 style.p 的 component,而 React 會動態隨機產生 className,並加入設定好的 class:

以切出簡單的 TodoItem 為例

根據上述範例,其實就是在 styled 後面寫 css 程式碼,因此也可寫成 Sass 語法:

const TodoItemWrapper = styled.div`
  max-width: 80%;
  margin: 5px auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border: 1px solid #eee;
`
const TodoContent = styled.div`
  color: #000;
`

// 不用傳入任何東西,但仍需在最後加上反引號
const TodoButtonWrapper = styled.div``

const Button = styled.button`
  padding: 4px;
  color: #232332;
  // 也可使用 Sass 語法
  &:hover {
    color: red;
  }
  & + & {
    margin-left: 4px;
  }
`

function App() {
  return (
    <div className="App">
      <TodoItemWrapper>
        <TodoContent>This is Todo</TodoContent>
        <TodoButtonWrapper>
          <Button>未完成</Button>
          <Button>刪除</Button>
        </TodoButtonWrapper>
      </TodoItemWrapper>
    </div>
  );
}

結果如下:

通常會把模板 TodoItem 獨立寫成 component,改寫後如下:

function TodoItem ({ size, content }) {
  return (
    <TodoItemWrapper>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <Button>未完成</Button>
        <Button>刪除</Button>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}

function App() {
  const titleSize = "M"
  return (
    <div className="App">
      <TodoItem content={123} />
      <TodoItem content={456} size="XL" />
    </div>
  );
}

也可以在 TodoContent 傳入參數 props,傳入參數的程式碼需寫在 ${...} 裡面,而在括號內的 css 程式碼則要用反引號包住:

const TodoContent = styled.div`
  color: #000;
  font-size: 12px;
  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

結果如下:

styled component 實戰

有關 styled component 套件的詳細功能可參考官方文件,接著要舉一些常用語法作為範例。

範例一:透過 styled() 繼承樣式

  • 如果是對 styled component 進行 restyle,裡面寫的 css 程式碼會蓋過原本的樣式:
// 繼承 Button 這個 styled component
const GreenButton = styled(Button)`
  background: green;
  color: #eee;
`

function TodoItem ({ size, content }) {
  return (
    <TodoItemWrapper>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <Button>未完成</Button>
        <GreenButton>刪除</GreenButton>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}
  • 如果是對一般的 component 進行 restyle,則需要在 component 傳入 className,用來接收 BlackTodoItem 這個 class 屬性:
// 繼承 TodoItem component,需要傳入 className
function TodoItem ({ className, size, content }) {
  return (
    <TodoItemWrapper className={className}>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <Button>未完成</Button>
        <GreenButton>刪除</GreenButton>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}
  // 繼承 TodoItem
  const BlackTodoItem = styled(TodoItem)`
    background: #000;
  `

function App() {
  const titleSize = "M"
  return (
    <div className="App">
      <TodoItem content={123} />
      <BlackTodoItem content={456} size="XL" />
    </div>
  );
}

範例二:透過 MEDIA QUERY 實作 RWD

像 MEDIA QUERY 這類通用性高的程式碼,可以獨立放在 constants\style.js 檔案,以便重複使用:

export const MEDIA_QUERY_MD = '@media screen and {min-width: 768px}'
export const MEDIA_QUERY_LG = '@media screen and {min-width: 1000px}'

接著就可直接在 App.js 引入使用:

import { MEDIA_QUERY_MD, MEDIA_QUERY_LG} from './constants/style';

const Button = styled.button`
  padding: 4px;
  color: #232332;
  font-size: 20px;
  
  ${MEDIA_QUERY_MD} {
    font-size 16px;
  }

  ${MEDIA_QUERY_LG} {
    font-size: 12px;
  }
  
  &:hover {
    color: red;
  }
  & + & {
    margin-left: 4px;
  }
`

RWD 結果如下:

範例三:使用 Sass 向量變數

透過傳入 Global 參數,我們能使用 Sass 向量變數。

舉例來說,我們可在 index.js 引入 ThemeProvider

import { ThemeProvider } from 'styled-components';

// 宣告 theme 變數
const theme = {
  colors: {
    primary_300: '#ff7777',
    primary_400: '#e33e3e',
    primary_500: '#af0505',
  }
}

ReactDOM.render(
  // 包住 App,並自訂 theme 屬性
  <ThemeProvider theme={theme}>
    <App />
  </ThemeProvider>,
  document.getElementById('root')
);

這樣就可以在 App.js 中取用這些變數:

const TodoContent = styled.div`
  font-size: 30px;
  color: ${props => props.theme.colors.primary_300};

  ${MEDIA_QUERY_MD} {
    font-size: 20px;
    color: ${props => props.theme.colors.primary_400};
  }

  ${MEDIA_QUERY_LG} {
    font-size: 12px;
    color: ${props => props.theme.colors.primary_500};
  }
`

也可以把 TodoItem component 獨立成 TodoItem.js 這個檔案,並且 export :

export default function TodoItem() {...}

並在 App.js 引入:

import TodoItem from './TodoItem'

這麼做的好處就是,依照功能切割程式碼,能夠達到模組化,進而提高程式碼可讀性。

參考資料:

  • [筆記] JavaScript ES6 中的模版字符串(template literals)和標籤模版(tagged template)
  • [第二十一週] React 基礎:如何寫 CSS