likes
comments
collection
share

在 React 项目中使用 Emotion (CSS in JS)

作者站长头像
站长
· 阅读数 15

这是我参与更文挑战的第 7 天,活动详情查看:更文挑战

CSS in JS 的优点

CSS in JS 已逐渐发展为 React 应用中写样式的一个主流的方案,著名组件库 material-ui 也已经使用 CSS in JS 来实现。 CSS in JS 的实现方式有两种: 唯一CSS选择器和内联样式。因此

  1. 不用关心繁琐的 Class 命名规则
  2. 不用担心样式被覆盖
  3. 便利的样式复用(样式都是 js 对象或字符串)
  4. 减少冗余的 CSS 代码,极致的样式按需加载

Emotion 是 CSS in JS 的众多实现方案中的其中一个,下面介绍一下它的使用。

说明:以下的介绍都来自于Emotion官方文档

Emotion 的基础使用

Object Styles 和 String Styles

Emotion 支持 js 对象js 字符串两种形式的样式定义。

Object Styles

/** @jsx jsx */
import { jsx } from '@emotion/react'

render(
  <div
    css={{
      backgroundColor: 'hotpink',
      '&:hover': {
        color: 'lightgreen'
      }
    }}
  >
    This has a hotpink background.
  </div>
)

String Styles

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/react'

const color = 'darkgreen'

render(
  <div
    css={css`
      background-color: hotpink;
      &:hover {
        color: ${color};
      }
    `}
  >
    This has a hotpink background.
  </div>
)

添加样式

Emotion 有两种写 CSS 的方式:css-propStyled Components

css Prop

点击这里查看如何开启 css prop

String Styles 示例

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/react'

const color = 'darkgreen'

render(
  <div
    css={css`
      background-color: hotpink;
      &:hover {
        color: ${color};
      }
    `}
  >
    This has a hotpink background.
  </div>
)

@emotion/react 的 jsx 是一个增强的 React.createElement 方法,它给 React 元素添加了一个 css prop。

Object Styles 示例

/** @jsx jsx */
import { jsx } from '@emotion/react'

render(
  <div
    css={{
      backgroundColor: 'hotpink',
      '&:hover': {
        color: 'lightgreen'
      }
    }}
  >
    This has a hotpink background.
  </div>
)

无论是Object Styles还是String Styles,我们都可以直接在定义样式的时候读取上下文的 js 变量,这个可以让我们很方便地更改样式。

Styled Components

Styled Components 基础用法 Styled Components 导出了一些带有 html 标签的内置组件,我们下面的语法就可以添加对应的样式。

import styled from '@emotion/styled'

let SomeComp = styled.div({
  color: 'hotpink'
})

let AnotherComp = styled.div`
  color: ${props => props.color};
`

render(
  <SomeComp>
    <AnotherComp color="green" />
  </SomeComp>
)

Styled Components 的 Props Styled Components 生成的组件也可以根据传入的 Props 来更改样式

import styled from '@emotion/styled'

const Button = styled.button`
  color: ${props =>
    props.primary ? 'hotpink' : 'turquoise'};
`

const Container = styled.div(props => ({
  display: 'flex',
  flexDirection: props.column && 'column'
}))

render(
  <Container column>
    <Button>This is a regular button.</Button>
    <Button primary>This is a primary button.</Button>
  </Container>
)

Styled Components 还有一些不常用的用法,点击这里了解更多

样式复用

在 Emotion 中,我们可以把通用样式用变量声明,然后在不同的组件中共享。

/** @jsx jsx */
import { jsx, css } from '@emotion/react'

const base = css`
  color: hotpink;
`

render(
  <div
    css={css`
      ${base};
      background-color: #eee;
    `}
  >
    This is hotpink.
  </div>
)

上面的 base 样式就可以在 render 时被使用。如果我们有其它的组件用到 base 样式,我们也可以导入 base 这个变量来使用。

样式优先级

/** @jsx jsx */
import { css, jsx } from '@emotion/react'

const danger = css`
  color: red;
`

const base = css`
  background-color: darkgreen;
  color: turquoise;
`

render(
  <div>
    <div css={base}>This will be turquoise</div>
    <div css={[danger, base]}>
      This will be also be turquoise since the base styles
      overwrite the danger styles.
    </div>
    <div css={[base, danger]}>This will be red</div>
  </div>
)

写样式的时候难免会需要覆盖样式的情况,这时候我们可以像上面一样调整 basedanger 的先后顺序来覆盖(后面的样式优先级较高)。

嵌套选择器

Emotion 样式也支持 css 选择器。

选择并设置组件内的节点

/** @jsx jsx */
import { jsx, css } from '@emotion/react'

const paragraph = css`
  color: turquoise;

  a {
    border-bottom: 1px solid currentColor;
    cursor: pointer;
  }
`
render(
  <p css={paragraph}>
    Some text.
    <a>A link with a bottom border.</a>
  </p>
)

当组件是子组件时,使用 & 来选择自己并设置样式

/** @jsx jsx */
import { jsx, css } from '@emotion/react'

const paragraph = css`
  color: turquoise;

  header & {
    color: green;
  }
`
render(
  <div>
    <header>
      <p css={paragraph}>
        This is green since it's inside a header
      </p>
    </header>
    <p css={paragraph}>
      This is turquoise since it's not inside a header.
    </p>
  </div>
)

媒体查询

/** @jsx jsx */
import { jsx, css } from '@emotion/react'

render(
  <p
    css={css`
      font-size: 30px;
      @media (min-width: 420px) {
        font-size: 50px;
      }
    `}
  >
    Some text!
  </p>
)

点击这里了解更多媒体查询的用法

全局样式

import { Global, css } from '@emotion/react'

render(
  <div>
    <Global
      styles={css`
        .some-class {
          color: hotpink !important;
        }
      `}
    />
    <Global
      styles={{
        '.some-class': {
          fontSize: 50,
          textAlign: 'center'
        }
      }}
    />
    <div className="some-class">This is hotpink now!</div>
  </div>
)

进阶用法

keyframes

/** @jsx jsx */
import { jsx, css, keyframes } from '@emotion/react'

const bounce = keyframes`
  from, 20%, 53%, 80%, to {
    transform: translate3d(0,0,0);
  }

  40%, 43% {
    transform: translate3d(0, -30px, 0);
  }

  70% {
    transform: translate3d(0, -15px, 0);
  }

  90% {
    transform: translate3d(0,-4px,0);
  }
`

render(
  <div
    css={css`
      animation: ${bounce} 1s ease infinite;
    `}
  >
    some bouncing text!
  </div>
)

主题

ThemeProvider

import * as React from 'react'
import styled from '@emotion/styled'
import { ThemeProvider, withTheme } from '@emotion/react'

// object-style theme

const theme = {
  backgroundColor: 'green',
  color: 'red'
}

// function-style theme; note that if multiple <ThemeProvider> are used,
// the parent theme will be passed as a function argument

const adjustedTheme = ancestorTheme => ({ ...ancestorTheme, color: 'blue' })

class Container extends React.Component {
  render() {
    return (
      <ThemeProvider theme={theme}>
        <ThemeProvider theme={adjustedTheme}>
          <Text>Boom shaka laka!</Text>
        </ThemeProvider>
      </ThemeProvider>
    )
  }
}

withTheme

import * as PropTypes from 'prop-types'
import * as React from 'react'
import { withTheme } from '@emotion/react'

class TellMeTheColor extends React.Component {
  render() {
    return <div>The color is {this.props.theme.color}.</div>
  }
}

TellMeTheColor.propTypes = {
  theme: PropTypes.shape({
    color: PropTypes.string
  })
}

const TellMeTheColorWithTheme = withTheme(TellMeTheColor)

useTheme

/** @jsx jsx */
import { jsx, ThemeProvider, useTheme } from '@emotion/react'
import styled from '@emotion/styled'

const theme = {
  colors: {
    primary: 'hotpink'
  }
}

function SomeText(props) {
  const theme = useTheme()
  return <div css={{ color: theme.colors.primary }} {...props} />
}

render(
  <ThemeProvider theme={theme}>
    <SomeText>some text</SomeText>
  </ThemeProvider>
)

开启 css Prop

css Prop 可以在任何一个支持 className 的 Dom 元素或者组件上使用。

css Prop 需要通过 babel-preset 或者 JSX Pragma 来实现

babel-preset

.babelrc

{
  "presets": ["@emotion/babel-preset-css-prop"]
}

If you are using the compatible React version (>=16.14.0) then you can opt into using the new JSX runtimes by using such configuration:

如果 React 版本 >=16.14.0 , 可以使用如下的配置来使用新的 jsx 运行时。

{
  "presets": [
    [
      "@babel/preset-react",
      { "runtime": "automatic", "importSource": "@emotion/react" }
    ]
  ],
  "plugins": ["@emotion/babel-plugin"]
}

tsconfig.json

这里指的是使用 babel 编译 typescript 时的配置

{
  "compilerOptions": {
    ...
    // "jsx": "react",
    "jsxImportSource": "@emotion/react",
    ...
  }
}

JSX Pragma

通过添加 JSX 转换 的注释来开启 css Prop。

/** @jsx jsx */
import { jsx } from '@emotion/react'

/** @jsx jsx */ 不生效的时候可以改为 /** @jsxImportSource @emotion/react */ 来尝试。

点击这里查看详细的配置文档

兼容性

Emotion supports all popular browsers, including Internet Explorer 11.

参考资料