Skip to main content

[note] styled-component 筆記

Transient Props:避免 props 傳入到子層元件#

如果出現 React does not recognize the `fooBar` prop on a DOM element 的錯誤,表示 Styled Components 會把這個值傳到最終的 DOM 元素上,但 DOM 元素上並沒有 fooBar 這個屬性,所以會發生錯誤。

要避免這個問題只需要使用 transient props,也就是在變數前面加上 $ 的做法即可:

const TextWithAbnormalStatus = styled(Typography)<{
$fooBar: AbnormalStatus;
}>`
color: ${({ $fooBar }) => {
return $fooBar === 'transient' ? 'red' : 'blue';
}};
`;
const Example = () => <TextWithAbnormalStatus $fooBar="transient" />;

Styling any Components#

如果有第三方或自己寫的元件(components)想要變成 styled components 的話,只需在原本自己的元件上加上 className,讓 styled-components 可以為它添加 class 名稱

// This could be react-router-dom's Link for example
const Link = ({ className, children }) => <a className={className}>{children}</a>;
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
render(
<div>
<Link>Unstyled, boring Link</Link>
<br />
<StyledLink>Styled, exciting Link</StyledLink>
</div>,
);

Styling any components @ styled-components

Sample Code#

Styling any components @ CodeSandbox

Props#

如果你要 styled 的是:

  • 是 一般的 HTML 元素(例如 styled.div),styled-components 會把所有已知合法的 HTML Attributes 當成 props 傳入
  • 是 React 元件(例如,styled(MyComponent)),那麼 styled-components 會把所有的 props 當成 props 傳入。

如果是一般的 HTML 元素,想要加入非正規的 HTML 屬性時,需要使用 .attrs() 方法。

attrs#

透過 .attrs() 方法,除了自定 HTML 屬性放到 HTML 元素上,還可以使用動態的屬性(dynamic attributes)

const Input = styled.input.attrs({
// 這裡可以定義合法或自訂的 HTML attributes
type: 'password',
foo: 'bar',
// 也可以動態定義 HTML 屬性
margin: (props) => props.size || '1em',
padding: (props) => props.size || '1em',
})`
color: palevioletred;
font-size: 1em;
/* 在這裡可以直接拿動態的屬性來用 */
margin: ${(props) => props.margin};
padding: ${(props) => props.padding};
`;
render(
<div>
<Input placeholder="A small text input" size="1em" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</div>,
);

Attaching Additional Props @ styled-components

Sample Code#

Attaching Additional Props @ CodeSandbox

Refer to other Components#

const Text = styled.span`
font-weight: bold;
`;
const Headline = styled.h3`
padding: 5px 10px;
background: papayawhip;
color: gray;
// Headline 裡的 Text 會套用 color
// 也就是 Headline Text { ... } 的意思
${Text} {
color: blue;
}
`;
const Link = styled.a`
display: inline-flex;
align-items: center;
color: palevioletred;
// 當 Headline 被 hover 的時候, "&" 會套用 color
// 也就是 Headline:hover Link {...} 的意思
${Headline}:hover & {
color: rebeccapurple;
}
`;
const Watchout = () => (
<Headline>
<Text>Refer</Text> to <Link>Watchout</Link>
</Headline>
);

Refer to other components @ CodeSandbox

Animations#

在 CSS Animations 中 @keyframes 通常不會被縮限在一個元件中,但使用上卻又不希望它跑到全域造成命名衝突,因此在 styled-components 中有一個 keyframes 的 helper,讓我們可以使用同一個 @keyframes 但又不會有命名衝突:

import styled, { keyframes } from 'styled-components';
// 建立 keyframes
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// 使用定義好的 rotate keyframes
const Rotate = styled.div`
display: inline-block;
animation: ${rotate} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
render(<Rotate>&lt; 💅 &gt;</Rotate>);

Sample Code#

Animation @ CodeSandbox

Theme#

在 styled-components 中還提供 <ThemeProvider> 這個 wrapper 元件,這個元件透過 context API 可以把內層的所有 React 元件都提供 theme props。

// Define our button, but with the use of props.theme this time
// STEP 3:在原本的 React Component 中,可以透過 props.theme 取得先前定義好的物件
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* 根據 theme.main 顯示顏色 */
color: ${(props) => props.theme.main};
border: 2px solid ${(props) => props.theme.main};
`;
// 對於沒有綁定的可以使用預設值
Button.defaultProps = {
theme: {
main: 'palevioletred',
},
};
// STEP 1: 定義要帶入 theme props 的物件
const theme = {
main: 'mediumseagreen',
};
// STEP 2: 透過 <ThemeProvider> 把物件帶入 theme props 中
render(
<div>
<Button>Normal</Button>
<ThemeProvider theme={theme}>
<Button>Themed</Button>
</ThemeProvider>
</div>,
);

Sample Code#

ThemeProvider @ CodeSandbox

TypeScript#

TypeScript @ Styled Components

Sample Code#

react-styled-component-sandbox @ CodeSandbox

Using Custom Props#

Styled Component 在搭配 TypeScript 使用 props 時,只需要在 element 或 component 後定義帶入的型別即可:

// https://styled-components.com/docs/api#using-custom-props
enum AlertStatusEnum {
Unknown = 0,
Normal = 1,
Mild = 2,
Severe = 3,
}
interface StatusTextProps {
alertStatus: AlertStatusEnum;
}
const StatusText = styled.span<StatusTextProps>`
font-size: 20px;
line-height: 28px;
color: ${({ alertStatus }) => (alertStatus === AlertStatusEnum.Normal ? 'green' : 'red')};
`;
const AlertStatus = () => (
<StatusText alertStatus={AlertStatusEnum.Normal}>Custom Props of Styled Component</StatusText>
);
官方文件可能有誤

在 Styled Components 的官方文件中,會看到把型別定義在 styled 後,像是下方的寫法。但這文件似乎是錯誤的,可以參考 issue#3407 和這個 PR#758

const Title =
styled <
{ isActive: boolean } >
Header`
color: ${(props) => (props.isActive ? props.theme.primaryColor : props.theme.secondaryColor)}
`;

Using Styled Component#

如果是想要 styled 已經寫好的 component,作法是一樣的,但要留意:

  • Link 元件需要接收 className 來套用 styled-components 產生的樣式,且 className 的型別要是 optional 的,即 className?: string;(參考 caveat with className
// 建立 Link 元件
interface LinkProps {
text: string;
className?: string; // https://styled-components.com/docs/api#caveat-with-classname
}
const Link = ({ className, text }: LinkProps) => {
return (
<a className={className} href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
{text}
</a>
);
};
// 將 Link Component 添加樣式後,產生 StyledLink
interface StyledLinkProps {
fontSize: string;
}
const StyledLink = styled(Link)<StyledLinkProps>`
font-size: ${(props) => props.fontSize};
`;
// 使用 StyledLink
const App = () => {
return <StyledLink text="Learn React" fontSize="24px" />;
};

使用 Theme 和 ThemeProvider#

ThemeProvider 搭配 TypeScript 可以讓定義好的 theme 受到 TypeScript 型別的保護。

建立 styled.d.ts#

首先要 extends StyledComponent 內建的 DefaultTheme,因此在 src 資料夾中建立 styled.d.ts 的檔案,在裡面定義好所需的 theme 和型別:

// styled.d.ts
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
borderRadius: string;
colors: {
main: string;
secondary: string;
};
}
}

建立 theme#

接著建立要放入 ThemeProvider 中的 theme:

// theme.ts
import { DefaultTheme } from 'styled-components';
// 這裡會指定型別為剛剛擴展過的 DefaultTheme
const theme: DefaultTheme = {
borderRadius: '5px',
colors: {
main: 'cyan',
secondary: 'magenta',
},
};
export { theme };

使用 ThemeProvider#

接著一樣使用 ThemeProvider:

import App from './App';
import { ThemeProvider } from 'styled-components';
import theme from './theme';
ReactDOM.render(
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>,
document.getElementById('root'),
);

使用 typeof operator 讓 TypeScript 自動推斷 theme 的型別#

如果覺得需要再 styled.d.ts 中把所有 theme 都預先定義好太過麻煩的話,可以參考 Styled Components & TypeScript 的做法,利用 typeof 運算子來讓 TypeScript 自動推斷 theme 的型別:

// styled.d.ts
import 'styled-components';
import theme from './theme';
/**
* Typing the Theme
**/
declare module 'styled-components' {
// use typeof operator to auto generate type
// https://blog.agney.dev/styled-components-&-typescript/
type Theme = typeof theme;
export interface DefaultTheme extends Theme {}
}

這個做的話,theme.ts 中就不需要再匯入 defaultTheme,改成這樣即可:

const theme = {
borderRadius: '5px',
colors: {
main: 'cyan',
secondary: 'magenta',
},
};
export { theme };

Create Global Style#

  • 透過 createGlobalStyle 可以用來建立或覆蓋全域的 CSS 樣式
  • GlobalStyle 中也可以使用到 theme 中定義好的樣式
// GlobalStyled.ts
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
/* 覆蓋全域的 CSS 樣式 */
}
.night {
background-color: gray;
color: ${(props) => props.theme.colors.light}
}
`;
export default GlobalStyle;

使用:

// index.tsx
import { render } from 'react-dom';
import { ThemeProvider } from 'styled-components';
import theme from './theme/theme';
import App from './App';
import GlobalStyle from './theme/GlobalStyle';
const rootElement = document.getElementById('root');
render(
<ThemeProvider theme={theme}>
<GlobalStyle />
<App />
</ThemeProvider>,
rootElement,
);

Sample Code#

Create Global Style @ CodeSandbox

CSS Helper#

透過 css helper 定義 mixing:

// mixin.ts
import { css } from 'styled-components';
export const largeFontSize = css`
font-size: 36px;
font-weight: bold;
`;
export const normalFontSize = css`
font-size: 24px;
font-weight: normal;
`;
export const smallFontSize = css`
font-size: 12px;
font-weight: thin;
`;

可以直接在 Styled Components 中使用定義好的 mixins:

import { largeFontSize, normalFontSize, smallFontSize } from '../theme/mixins';
const StyledHeader = styled.div<StyledHeaderProp>`
// 使用定義好的 mixins
${(props) =>
props.fontSize === 'large'
? largeFontSize
: props.fontSize === 'small'
? smallFontSize
: normalFontSize};
`;

或者如果需要在 Styled Components 中根據 props 一次建立多個 CSS 樣式時:

import styled, { css } from 'styled-components';
const StyledHeader = styled.div<StyledHeaderProp>`
// 直接根據 props 動態變化許多樣式
${(props) =>
props.fontSize === 'large'
? css`
font-size: 36px;
font-weight: bold;
`
: css`
font-size: 24px;
font-weight: normal;
`}
`;

CSS Helper

VSCode#

在搭配 VSCode 使用 styed-component 時,若遇到無法 "Fold All" 該程式碼區塊時,可以加上以下設定:

"[javascript]": {
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.renderWhitespace": "all",
"editor.foldingStrategy": "indentation",
},
"[javascriptreact]": {
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.renderWhitespace": "all",
"editor.foldingStrategy": "indentation"
},

參考文章#

Last updated on