嵌套与选择器 #

基本概念 #

Emotion 支持 CSS 嵌套语法,类似于 Sass/Less,让你可以更直观地编写组件样式。嵌套语法让样式更加内聚,便于维护。

基本嵌套 #

父子关系 #

使用 & 引用父选择器:

jsx
import styled from '@emotion/styled'

const Card = styled.div`
  padding: 20px;
  background: white;
  border-radius: 8px;
  
  & h2 {
    margin-top: 0;
    color: #333;
  }
  
  & p {
    color: #666;
    line-height: 1.6;
  }
  
  & button {
    margin-top: 12px;
  }
`

直接子元素 #

使用 > 选择直接子元素:

jsx
import styled from '@emotion/styled'

const List = styled.ul`
  list-style: none;
  padding: 0;
  
  & > li {
    padding: 12px;
    border-bottom: 1px solid #eee;
    
    &:last-child {
      border-bottom: none;
    }
  }
`

伪类选择器 #

基本伪类 #

jsx
import styled from '@emotion/styled'

const Button = styled.button`
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  &:hover {
    background: #0056b3;
  }
  
  &:active {
    transform: scale(0.98);
  }
  
  &:focus {
    outline: 2px solid #007bff;
    outline-offset: 2px;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`

表单伪类 #

jsx
import styled from '@emotion/styled'

const Input = styled.input`
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 4px;
  outline: none;
  transition: border-color 0.2s ease;
  
  &:focus {
    border-color: #007bff;
  }
  
  &:hover:not(:focus) {
    border-color: #bbb;
  }
  
  &:disabled {
    background: #f5f5f5;
    cursor: not-allowed;
  }
  
  &:read-only {
    background: #f9f9f9;
  }
  
  &:placeholder-shown {
    font-style: italic;
  }
  
  &:valid {
    border-color: #28a745;
  }
  
  &:invalid:not(:placeholder-shown) {
    border-color: #dc3545;
  }
`

const Checkbox = styled.input`
  width: 18px;
  height: 18px;
  cursor: pointer;
  
  &:checked {
    accent-color: #007bff;
  }
`

结构伪类 #

jsx
import styled from '@emotion/styled'

const Table = styled.table`
  width: 100%;
  border-collapse: collapse;
  
  & tr {
    &:nth-child(even) {
      background: #f9f9f9;
    }
    
    &:nth-child(odd) {
      background: white;
    }
    
    &:first-child {
      background: #007bff;
      color: white;
    }
    
    &:last-child {
      border-bottom: 2px solid #007bff;
    }
  }
  
  & td, & th {
    padding: 12px;
    text-align: left;
  }
`

const Grid = styled.div`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  
  & > div {
    &:first-of-type {
      grid-column: span 3;
    }
    
    &:nth-of-type(2),
    &:nth-of-type(3) {
      grid-column: span 1.5;
    }
  }
`

伪元素选择器 #

基本伪元素 #

jsx
import styled from '@emotion/styled'

const Tooltip = styled.span`
  position: relative;
  cursor: help;
  
  &::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding: 8px 12px;
    background: #333;
    color: white;
    font-size: 12px;
    white-space: nowrap;
    border-radius: 4px;
    opacity: 0;
    visibility: hidden;
    transition: all 0.2s ease;
  }
  
  &:hover::after {
    opacity: 1;
    visibility: visible;
  }
`

const RequiredLabel = styled.label`
  &::after {
    content: ' *';
    color: #dc3545;
  }
`

装饰性伪元素 #

jsx
import styled from '@emotion/styled'

const Card = styled.div`
  position: relative;
  padding: 24px;
  background: white;
  border-radius: 8px;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background: #007bff;
    border-radius: 8px 0 0 8px;
  }
`

const Quote = styled.blockquote`
  position: relative;
  padding: 20px 30px;
  margin: 0;
  font-style: italic;
  color: #666;
  
  &::before {
    content: '"';
    position: absolute;
    top: -10px;
    left: 10px;
    font-size: 60px;
    color: #007bff;
    font-family: Georgia, serif;
  }
`

const Clearfix = styled.div`
  &::after {
    content: '';
    display: table;
    clear: both;
  }
`

兄弟选择器 #

相邻兄弟选择器 #

使用 + 选择相邻元素:

jsx
import styled from '@emotion/styled'

const FormField = styled.div`
  margin-bottom: 16px;
  
  & label {
    display: block;
    margin-bottom: 4px;
    font-weight: bold;
  }
  
  & label + input {
    margin-top: 4px;
  }
  
  & input + span {
    display: block;
    margin-top: 4px;
    font-size: 12px;
    color: #666;
  }
`

通用兄弟选择器 #

使用 ~ 选择所有兄弟元素:

jsx
import styled from '@emotion/styled'

const RadioGroup = styled.div`
  & input[type="radio"] {
    display: none;
    
    &:checked + label {
      background: #007bff;
      color: white;
    }
    
    &:checked ~ label {
      opacity: 0.5;
    }
  }
  
  & label {
    display: inline-block;
    padding: 8px 16px;
    background: #f5f5f5;
    cursor: pointer;
    transition: all 0.2s ease;
  }
`

属性选择器 #

基本属性选择 #

jsx
import styled from '@emotion/styled'

const Input = styled.input`
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 4px;
  
  &[type="text"] {
    font-family: inherit;
  }
  
  &[type="password"] {
    letter-spacing: 2px;
  }
  
  &[type="email"] {
    background-image: url('email-icon.svg');
    background-repeat: no-repeat;
    background-position: right 12px center;
  }
  
  &[type="number"] {
    text-align: right;
  }
  
  &[disabled] {
    background: #f5f5f5;
    cursor: not-allowed;
  }
  
  &[readonly] {
    background: #f9f9f9;
  }
`

属性值匹配 #

jsx
import styled from '@emotion/styled'

const Link = styled.a`
  color: #007bff;
  text-decoration: none;
  
  &[href^="http"] {
    &::after {
      content: ' ↗';
    }
  }
  
  &[href$=".pdf"] {
    &::before {
      content: '📄 ';
    }
  }
  
  &[href*="github"] {
    color: #333;
  }
  
  &[target="_blank"] {
    &::after {
      content: ' 🔗';
    }
  }
`

组件选择器 #

Emotion 组件选择器 #

使用 ${Component} 语法选择 Emotion 组件:

jsx
import styled from '@emotion/styled'

const Icon = styled.span`
  margin-right: 8px;
`

const Button = styled.button`
  display: inline-flex;
  align-items: center;
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  
  &:hover ${Icon} {
    transform: translateX(4px);
  }
`

const Container = styled.div`
  padding: 20px;
  
  &:hover ${Button} {
    background: #0056b3;
  }
`

嵌套组件选择器 #

jsx
import styled from '@emotion/styled'

const Card = styled.div`
  padding: 20px;
  background: white;
  border-radius: 8px;
`

const CardTitle = styled.h3`
  margin: 0 0 12px;
  color: #333;
`

const CardBody = styled.div`
  color: #666;
`

const CardFooter = styled.div`
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #eee;
`

const FeaturedCard = styled(Card)`
  border: 2px solid gold;
  
  ${CardTitle} {
    color: #b8860b;
  }
  
  ${CardBody} {
    color: #333;
  }
  
  ${CardFooter} {
    border-top-color: gold;
  }
`

深度选择器 #

全局选择器 #

在组件内使用全局选择器:

jsx
import styled from '@emotion/styled'

const MarkdownContent = styled.div`
  line-height: 1.6;
  
  & h1 {
    font-size: 32px;
    margin-bottom: 16px;
  }
  
  & h2 {
    font-size: 24px;
    margin-bottom: 12px;
  }
  
  & p {
    margin-bottom: 12px;
  }
  
  & code {
    background: #f5f5f5;
    padding: 2px 6px;
    border-radius: 4px;
    font-family: monospace;
  }
  
  & pre {
    background: #f5f5f5;
    padding: 16px;
    border-radius: 8px;
    overflow-x: auto;
    
    & code {
      background: none;
      padding: 0;
    }
  }
`

覆盖第三方组件样式 #

jsx
import styled from '@emotion/styled'

const StyledReactSelect = styled.div`
  .react-select__control {
    border: 2px solid #ddd;
    border-radius: 8px;
    
    &:hover {
      border-color: #007bff;
    }
  }
  
  .react-select__control--is-focused {
    border-color: #007bff;
    box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
  }
  
  .react-select__option--is-selected {
    background: #007bff;
  }
  
  .react-select__option--is-focused {
    background: #e9ecef;
  }
`

嵌套最佳实践 #

1. 避免过度嵌套 #

❌ 过度嵌套:

jsx
const Bad = styled.div`
  & > section {
    & > article {
      & > div {
        & > p {
          color: red;
        }
      }
    }
  }
`

✅ 合理嵌套:

jsx
const Good = styled.div`
  & p {
    color: red;
  }
`

2. 使用有意义的类名 #

jsx
const Card = styled.div`
  & .card-header {
    padding: 16px;
    border-bottom: 1px solid #eee;
  }
  
  & .card-body {
    padding: 16px;
  }
  
  & .card-footer {
    padding: 16px;
    background: #f9f9f9;
  }
`

3. 分离复杂选择器 #

jsx
const baseStyles = css`
  & h1, & h2, & h3 {
    margin-top: 0;
  }
  
  & p, & ul, & ol {
    margin-bottom: 16px;
  }
`

const Article = styled.article`
  ${baseStyles}
  line-height: 1.6;
`

下一步 #

掌握了嵌套和选择器后,继续学习 主题定制,了解如何实现应用的主题系统。

最后更新:2026-03-28