有種用了就回不去的感覺。
styled component 中的 props
既然它是 Component,那當然也有 props
可以用,直接來示範怎麼用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const Button = styled.button` border-radius: 4px; padding: 8px 12px; background-color: #e4e4e4; border: none; color: black; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; }
${ props => props.isDone && ` background-color: #0e920e66; color: white; ` } `
function TodoItem ({ content, handlebuttonClick }) { return ( <TodoItemWrapper> <TodoContent>{content}</TodoContent> <TodoButtonWrapper> // 在這裡傳入 <Button isDone={true}>已完成</Button> <Button onClick={handlebuttonClick}>刪除</Button> </TodoButtonWrapper> </TodoItemWrapper> ) }
|
在 styled Component 接收 props 的方式是透過 ${...}
,${...}
裡面會寫一個 function 來接收 props
參數,你就可以根據它來寫不同的 style 了。
繼承 style
這個跟 class 中的繼承有點類似,你可以先繼承某個 styled component, 再下新的 style 來覆寫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const Button = styled.button` border-radius: 4px; padding: 8px 12px; background-color: #e4e4e4; border: none; color: black; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; }
${ props => props.size === 'XL' ? 'font-size:20px' : 'font-size: 12px' } `
const PinkButton = styled(Button)` background-color: pink; `
|
如果要對「React」的 component 重新設定 style
先講一個觀念:
只有 styled component 才吃的到樣式,所以你用的時候一定是放 styled component,不是 react component。
用 React component 包裝起來的 styled components
這種要搭配 props
來傳入 className:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function TodoItem ({ className, children, handlebuttonClick }) { return ( <TodoItemWrapper className={className}> <TodoContent>{children}</TodoContent> <TodoButtonWrapper> <Button>已完成</Button> <Button onClick={handlebuttonClick}>刪除</Button> </TodoButtonWrapper> </TodoItemWrapper> ) }
const TodoItem2 = styled(TodoItem)` background-color: #d6d6d6; `
|
- 用 style component 來包裝的 React component
跟剛剛反過來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const OrangeButton = styled(TestButton)` ${defaultButton} border-color: ${({ theme }) => theme.orange}; color: ${({ theme }) => theme.orange}; &:hover { background-color: ${({ theme }) => theme.orange}; } `;
function TestButton ({ className }) { return <button className={className}>測試用</button> }
|
Medai query
其實就是直接在 styled 中寫 @media
就行了,不過更好的做法是把 breakpoint 拆出來寫成常數引入會更好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { MEDIA_TABLET, MEDIA_PC } from './constants/breakpoint'
const Button = styled.button` border-radius: 4px; padding: 8px 12px; background-color: #e4e4e4; border: none; color: black; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; }
${MEDIA_TABLET} { font-size: 1.2em; }
${MEDIA_PC} { font-size: 1.5em; }
`
|
變數的運用
首先要用 <ThemeProvider>
把整個 component 給包住:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { ThemeProvider } from 'styled-components';
const theme = { blue: 'royalblue', orange: 'darkorange', green: 'mediumseagreen', red: 'palevioletred', }
ReactDOM.render( <ThemeProvider theme={theme}> <App /> </ThemeProvider>, document.getElementById('root') )
|
接著在其他的 style component 就可以透過 props.theme
來存取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const Button = styled.button` border-radius: 4px; padding: 8px 12px; background-color: transparent; border: 1px solid ${props => props.theme.blue}; color: ${props => props.theme.blue}; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; } `
const GreenButton = styled(Button)` border-color: ${props => props.theme.green}; color: ${props => props.theme.green}; ` const OrangeButton = styled(Button)` border-color: ${props => props.theme.orange}; color: ${props => props.theme.orange}; ` const RedButton = styled(Button)` border-color: ${props => props.theme.red}; color: ${props => props.theme.red}; `
|
成果大概就像這樣:
不同的標籤想套用相同樣式
假設我寫了一個 <button>
的 styled component,但今天又想在 <a>
上用一樣的樣式時,不需要重新寫一遍,只要利用 as
就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const Button = styled.button` border-radius: 4px; padding: 8px 12px; background-color: transparent; border: 1px solid ${props => props.theme.blue}; color: ${props => props.theme.blue}; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; } &:hover { background-color: ${props => props.theme.blue}; color: white; } `
function TestScope () { return ( <TodoButtonWrapper style={{ marginTop: '20px' }}> <Button>Button</Button> // 用 as 變成希望的標籤 <Button as="a" href="#">Link</Button> <Button as="a" href="#">Link</Button> </TodoButtonWrapper> ) }
|
或甚至是變成另一個 component 也行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function TestScope () { return ( <TodoButtonWrapper style={{ marginTop: '20px' }}> <Button>Button</Button> <Button as={ReversedButton}>Button</Button> <ReversedButton>顛倒文字的按鈕</ReversedButton>
</TodoButtonWrapper> ) }
function ReversedButton ({ children }) { return <Button>{children.split('').reverse()}</Button> }
|
自動判斷是不是 HTML 元素的屬性
1 2 3 4 5 6 7
| <TodoItemWrapper> <TodoContent>{todo.content}</TodoContent> <TodoButtonWrapper> <GreenButton isDone={todo.isDone} data-id={todo.id} onClick={handleCompletedButtonClick}>{todo.isDone ? '已完成' : '未完成'}</GreenButton> <RedButton data-id={todo.id} onClick={handleRemoveButtonClick}>刪除</RedButton> </TodoButtonWrapper> </TodoItemWrapper>
|
以這個例子來說,GreenButton
寫了 isDone
和 data-id
兩個屬性,但實際上會被渲染出來的只有 data-id,不會有 isDone
:
這就是 style component 會做的自動判斷。
不過有一種情況是可能我想在 style component 傳 id
這個 props:
1 2 3 4
| <GreenButton id={todo.id} onClick={handleButtonClick('isCompleted')}>{todo.isDone ? '已完成' : '未完成'} </GreenButton>
|
可是又不希望被渲染到 HTML 上,這時候就可以改用 $
來傳(Transient props):
1 2 3 4
| <GreenButton $id={todo.id} onClick={handleButtonClick('isCompleted')}>{todo.isDone ? '已完成' : '未完成'} </GreenButton>
|
這樣就不會被渲染了。
所以建議養成一種習慣,只要是給 style component 用的 props 就一律用 $
來表示,可讀性會更好。
設定屬性值
要在 style component 上設定 HTML 元素的屬性有兩種方式,第一種是直接寫在 Component 上:
1 2 3 4 5 6 7 8 9 10 11 12
| const RadioButton = styled.input``
function TestScope () { return ( <TodoButtonWrapper style={{ marginTop: '20px' }}> <RadioButton type="radio" name='gender' value="man"></RadioButton> <RadioButton type="radio" name='gender' value="female"></RadioButton> </TodoButtonWrapper> ) }
|
第二種是透過 style.attrs
屬性:
1 2 3 4 5 6 7 8 9 10 11 12
| const RadioButton = styled.input.attrs({ type: 'radio' })``
function TestScope () { return ( <TodoButtonWrapper style={{ marginTop: '20px' }}> <RadioButton name='gender' value="man"></RadioButton> <RadioButton name='gender' value="female"></RadioButton> </TodoButtonWrapper> ) }
|
看起來是第二種會好一點,因為只要寫在一個地方就好,之後要改會比較方便。
設定全域空間的樣式
當想要改 <body>
或是 reset 的樣式時,應該就會用到。這是透過 createGlobalStyle
來達成的。
首先要先寫好全域的 style component:
1 2 3 4 5 6 7 8 9 10 11
| import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle` body { // 全域空間的 style background-color: pink; } .bg-dark { background-color: black; } `
|
接著引入到 entry 就會套用了:
1 2 3 4 5 6 7 8 9
| ReactDOM.render( <ThemeProvider theme={theme}> // 放在這裡,不是外面 // 另外因為是包在 ThemeProvide 裡,所以也能存到 theme 的 props <GlobalStyle /> <App /> </ThemeProvider>, document.getElementById('root') )
|
定義在 Global 的 className 也可以在底下的 component 使用:
1 2 3 4 5 6 7 8 9
| function TestScope () { return ( <TodoButtonWrapper className="bg-dark"> <Button>123</Button> <Button>456</Button> <Button>789</Button> </TodoButtonWrapper> ) }
|
給選取器更高的權重:&&
先來看段 code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const Span = styled.span` && { color: blue } `
const GlobalStyle = createGlobalStyle` body { // 全域空間的 style } span${Span} { color: red; } `
ReactDOM.render( <ThemeProvider theme={theme}> <GlobalStyle /> <App /> <Span>I'm span</Span> </ThemeProvider>, document.getElementById('root') )
|
簡單來說,我在 Global 宣告 span${Span}
要是紅色,但我希望實際是藍色,所以就在 Span
中用 && 來覆寫全域設定:
注意 &
跟 &&
的差別,一個會被覆寫一個不會。
把會重複使用的樣式存起來
style component 有提供 css
方法讓你把會共用的樣式儲存起來,它的寫法是這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import { css } from 'styled-components'
const buttonDefault = () => css` border-radius: 4px; padding: 8px 12px; background-color: transparent; cursor: pointer; flex-shrink: 0;
// 這個寫法會有一些問題,留到下面來解釋 & + & { margin-left: 8px; } ` const BlueButton = styled.button` // 直接引用 ${buttonDefault} border: 1px solid ${props => props.theme.blue}; color: ${props => props.theme.blue}; &:hover { background-color: ${props => props.theme.blue}; color: white; } ` const GreenButton = styled.button` // 直接引用 ${buttonDefault} border-color: 'green'; color: 'green'; &:hover { background-color: 'green'; } ` const OrangeButton = styled.button` // 直接引用 ${buttonDefault} border-color: 'orange'; color: 'orange'; &:hover { background-color: 'orange'; } ` const RedButton = styled.button` // 直接引用 ${buttonDefault} border-color: 'red'; color: 'red'; &:hover { background-color: 'red'; } `
|
這樣就能做出這樣的效果:
不過會發現 & + &
的部分並沒有套用到,為什麼?
這是因為當你建立一個新的 style component 時其實會重新產生一個 className,所以你的 & + &
其實是這樣子:
1 2 3 4 5 6 7 8 9
| BlueButton + BlueButton { margin-left: 8px } greenButton + greenButton { margin-left: 8px } RedButton + RedButton { margin-left: 8px }
|
上圖中的綠按鈕旁邊接的是紅按鈕,所以才沒有套用這個規則。
要解決這個問題的辦法有兩種,一種是改用「繼承」的寫法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const BlueButton = styled.button` border-radius: 4px; padding: 8px 12px; background-color: transparent; border: 1px solid ${props => props.theme.blue}; color: ${props => props.theme.blue}; cursor: pointer; flex-shrink: 0; & + & { margin-left: 8px; } &:hover { background-color: ${props => props.theme.blue}; color: white; } ` const GreenButton = styled(BlueButton)` // ... ` const OrangeButton = styled(BlueButton)` // ... ` const RedButton = styled(BlueButton)` // ... `
|
因為是透過繼承,所以 BlueButton
的部分不會重新產生 className,只有後來新增的才會。
另一種方式是把 & + &
的規則透過父層來指定:
1 2 3 4 5 6 7 8
| const TodoButtonWrapper = styled.div` display: flex; align-items: center; // 底下的 button + button 會套用 button + button { margin-left: 8px; } `
|
至於哪種寫法比較好就見仁見智,我個人是覺得繼承的寫法比較直覺一點,不過缺點是元件本身會多一條限制,因此「可重用性」會比較差。
其他補充
style.button
跟 style('button')
是等價的東西
- 不要把 style component 寫在 React component 裡面,會有效能問題(每次 render 就重新宣告)