项目结构 #

基本概念 #

良好的项目结构可以提高代码的可维护性和可读性。本节介绍 Emotion 项目的推荐组织方式。

目录结构 #

基础结构 #

text
src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.styles.ts
│   │   ├── Button.types.ts
│   │   └── index.ts
│   ├── Input/
│   ├── Card/
│   └── index.ts
├── styles/
│   ├── theme.ts
│   ├── global.ts
│   ├── mixins.ts
│   └── breakpoints.ts
├── layouts/
│   ├── MainLayout.tsx
│   └── MainLayout.styles.ts
├── pages/
│   ├── Home.tsx
│   └── About.tsx
└── App.tsx

组件内聚结构 #

将样式与组件放在一起:

text
src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.styles.ts
│   │   ├── Button.test.tsx
│   │   └── index.ts
│   └── Card/
│       ├── Card.tsx
│       ├── Card.styles.ts
│       ├── CardHeader.tsx
│       ├── CardBody.tsx
│       ├── CardFooter.tsx
│       └── index.ts

样式分离结构 #

将所有样式集中管理:

text
src/
├── components/
│   ├── Button.tsx
│   ├── Input.tsx
│   └── Card.tsx
├── styles/
│   ├── components/
│   │   ├── button.styles.ts
│   │   ├── input.styles.ts
│   │   └── card.styles.ts
│   ├── theme.ts
│   └── global.ts

文件命名规范 #

组件文件 #

文件类型 命名 说明
组件 ComponentName.tsx PascalCase
样式 ComponentName.styles.ts 样式定义
类型 ComponentName.types.ts TypeScript 类型
测试 ComponentName.test.tsx 测试文件
索引 index.ts 导出文件

样式文件示例 #

Button.styles.ts:

tsx
import styled from '@emotion/styled'
import { css } from '@emotion/react'

export const buttonBase = css`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.2s ease;
`

export const Button = styled.button`
  ${buttonBase}
  background-color: ${props => props.theme.colors.primary};
  color: white;
  
  &:hover {
    opacity: 0.9;
  }
`

export const OutlineButton = styled.button`
  ${buttonBase}
  background: transparent;
  border: 2px solid ${props => props.theme.colors.primary};
  color: ${props => props.theme.colors.primary};
`

Button.tsx:

tsx
import { Button, OutlineButton } from './Button.styles'

export { Button, OutlineButton }

组件组织 #

单文件组件 #

适合简单组件:

tsx
import styled from '@emotion/styled'

const Container = styled.div`
  padding: 20px;
  background: #f5f5f5;
`

const Title = styled.h2`
  font-size: 24px;
  color: #333;
`

export function SimpleCard({ title, children }) {
  return (
    <Container>
      <Title>{title}</Title>
      {children}
    </Container>
  )
}

多文件组件 #

适合复杂组件:

Card.styles.ts:

tsx
import styled from '@emotion/styled'

export const Container = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
`

export const Header = styled.div`
  padding: 16px;
  border-bottom: 1px solid #eee;
`

export const Body = styled.div`
  padding: 16px;
`

export const Footer = styled.div`
  padding: 16px;
  background: #f9f9f9;
`

Card.tsx:

tsx
import { Container, Header, Body, Footer } from './Card.styles'

export function Card({ header, children, footer }) {
  return (
    <Container>
      {header && <Header>{header}</Header>}
      <Body>{children}</Body>
      {footer && <Footer>{footer}</Footer>}
    </Container>
  )
}

Card.Header = Header
Card.Body = Body
Card.Footer = Footer

样式组织 #

主题文件 #

styles/theme.ts:

tsx
export const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
    info: '#17a2b8',
  },
  fontSizes: {
    xs: '12px',
    sm: '14px',
    md: '16px',
    lg: '18px',
    xl: '20px',
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px',
  },
}

export type Theme = typeof theme

Mixins 文件 #

styles/mixins.ts:

tsx
import { css } from '@emotion/react'

export const mixins = {
  flexCenter: css`
    display: flex;
    align-items: center;
    justify-content: center;
  `,
  
  flexBetween: css`
    display: flex;
    align-items: center;
    justify-content: space-between;
  `,
  
  truncate: css`
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  `,
  
  cardShadow: css`
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  `,
  
  smoothTransition: css`
    transition: all 0.2s ease;
  `,
  
  visuallyHidden: css`
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  `,
}

断点文件 #

styles/breakpoints.ts:

tsx
import { css } from '@emotion/react'

export const breakpoints = {
  sm: '576px',
  md: '768px',
  lg: '992px',
  xl: '1200px',
}

export const media = {
  sm: (styles) => css`
    @media (min-width: ${breakpoints.sm}) {
      ${styles}
    }
  `,
  md: (styles) => css`
    @media (min-width: ${breakpoints.md}) {
      ${styles}
    }
  `,
  lg: (styles) => css`
    @media (min-width: ${breakpoints.lg}) {
      ${styles}
    }
  `,
  xl: (styles) => css`
    @media (min-width: ${breakpoints.xl}) {
      ${styles}
    }
  `,
}

组件库结构 #

UI 组件库 #

text
src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.styles.ts
│   │   ├── Button.types.ts
│   │   ├── Button.stories.tsx
│   │   └── index.ts
│   ├── Input/
│   ├── Select/
│   ├── Checkbox/
│   └── index.ts
├── styles/
│   ├── theme.ts
│   ├── global.ts
│   └── index.ts
└── index.ts

导出结构 #

components/index.ts:

tsx
export { Button } from './Button'
export { Input } from './Input'
export { Select } from './Select'
export { Checkbox } from './Checkbox'

index.ts:

tsx
export * from './components'
export * from './styles'

最佳实践 #

1. 一致的命名 #

tsx
const PrimaryButton = styled.button`...`
const SecondaryButton = styled.button`...`
const DangerButton = styled.button`...`

2. 样式复用 #

tsx
const baseInputStyles = css`
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
`

const Input = styled.input`
  ${baseInputStyles}
`

const Textarea = styled.textarea`
  ${baseInputStyles}
  min-height: 100px;
`

3. 组件分组 #

tsx
const Card = styled.div`...`
const CardHeader = styled.div`...`
const CardBody = styled.div`...`
const CardFooter = styled.div`...`

Card.Header = CardHeader
Card.Body = CardBody
Card.Footer = CardFooter

export { Card }

4. 类型导出 #

tsx
export type { ButtonProps } from './Button.types'
export { Button } from './Button'

下一步 #

掌握了项目结构后,继续学习 Emotion与TypeScript,了解如何在 TypeScript 项目中使用 Emotion。

最后更新:2026-03-28