banner
他山之石

他山之石

在React和TypeScript中使用emotion

本文介绍如何在 React 项目中使用 CSS in JS 方案:emotion。

image

emotion#

emotion 是一种高性能且灵活的 CSS-in-JS 库。它本身与框架无关,你可以在 vue 或者 react 中搭配使用。目前我们使用的是 react,已经有多个项目在生产环境稳定运行。emotion11 是对 emotion10 的略微改进,主要侧重于开发者的体验,TS 的类型改进,以及使用新版本的解析器:Stylis。
本文主要介绍在 React 和 TypeScript 中如何集成 emotion11。

变化#

包重命名#

emotion11 最重要的变化之一是大部分面向用户的 package 都已经重命名。

重命名包的列表:

  • @emotion/core → @emotion/react
  • emotion → @emotion/css
  • emotion-theming → moved into @emotion/react
  • emotion-server → @emotion/server
  • create-emotion → @emotion/css/create-instance
  • create-emotion-server → @emotion/server/create-instance
  • babel-plugin-emotion → @emotion/babel-plugin
  • eslint-plugin-emotion → @emotion/eslint-plugin
    jest-emotion → @emotion/jest

Hooks#

在内部使用钩子以优化包的大小并在 React DevTools 中展示更好的 DOM 树。

TypeScript#

TypeScript 类型已经被完全重写。#
  1. 减少使用 emotion 时的构建时间,尤其是在大型项目中。
  2. 在许多情况下,不再需要为 emotion 组件手动指定通用参数
  3. 作为 props 的联合类型得到了更好的支持,应该正确推断
  4. 限制了 css 函数以防止传递无效的类型
  5. styled 的通用参数已更改,如果您指定 ComponentType,则需要删除该通用参数
  6. styled 不再需要第二个参数 ExtraProps,代替地将其移动至 styled 调用之后。因此,styled<typeof MyComponent, ExtraProps>(MyComponent) 应该被改写为 styled (MyComponent)({})
Theme 类型#

现在,为主题提供类型更加容易。您可以像这样创建内置的 Theme 接口,而不是像以前那样创建自定义实例:

import '@emotion/react'

declare module '@emotion/react' {
  export interface Theme {
    primaryColor: string
    secondaryColor: string
  }
}
css prop 类型#

基于使用的不同 JSX 运行时,emotion11 为 css prop 提供 TypeScript 支持的方式已经更改,可以仅对支持 className prop 的组件添加对应 css prop 的支持(因为 emotion 的 JSX 工厂函数采用提供的 css prop,对其解析并将生成的 className 传递给渲染的组件)。

Stylis V4#

emotion 使用的 css 解析器 Stylis 得到了升级,它修复了一些长期存在的解析边缘情况,同时变得更小,更快。

Emotion 的缓存#

创建高速缓存的自定义实例时,现在需要 key 选项。请确保它是唯一的(并且不等于 “css”),因为它用于将样式链接到缓存。如果多个缓存共享同一个键,它们可能会为彼此的样式元素 “争斗”。 新的 prepend 选项可以使 Emotion 在指定 DOM 容器的开头而不是结尾处添加样式标签。

使用#

安装#

yarn add @emotion/react

使用#

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

const style = css`
  color: hotpink;
`

const SomeComponent = ({ children }) => (
  <div css={style}>
    Some hotpink text.
    {children}
  </div>
)

const anotherStyle = css({
  textDecoration: 'underline'
})

const AnotherComponent = () => (
  <div css={anotherStyle}>Some text with an underline.</div>
)
render(
  <SomeComponent>
    <AnotherComponent />
  </SomeComponent>
)

css prop#

emotion 提供主要的书写 style 的方式是使用 css prop,它提供了一个简洁灵活的 API 来对组件进行样式设定。

有两种方式使用 css prop:

Babel Preset#

Babel 预设可在使用 classic JSX 运行时时自动启用 Emotion 的 css prop。如果要使用新的 JSX 运行时,请不要使用此预设,而应使用 @emotion/babel-plugin

  • 安装
yarn add @emotion/babel-preset-css-prop
  • 使用

.babelrc

{
 "presets": [
   [
     "@emotion/babel-preset-css-prop",
     {
       "autoLabel": "dev-only",
       "labelFormat": "[local]"
     }
   ]
 ],
}

如果您使用兼容的 React 版本(>=16.14.0),则可以通过以下配置选择使用新的 JSX 运行时:

.babelrc

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

JSX Pragma#

在使用 CSS prop 的源文件顶部设置 jsx 编译指示。此选项最适合测试 css prop 功能或在无法配置 babel 配置的项目(create-react-app,codesandbox 等)中。

/** @jsx jsx */

与包含 linter 配置的注释类似,此配置将 jsx babel 插件配置为使用 jsx 函数而不是 React.createElement。

如果您正在使用零配置工具来自动检测应该使用哪个运行时(classic 还是 automatic),并且您已经在使用具有新 JSX 运行时的 React 版本(因此运行时为您自动配置了 runtime: 'automatic'),例如 Create React App 4,然后

/ ** @jsx jsx * /

编译指示可能不起作用,您应该使用

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

使用 css prop#

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

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

主题#

Theme 包含在 @ emotion /react 包中。

将 ThemeProvider 添加到应用程序的顶层,并在样式化组件中使用 props.theme 来访问主题,或者提供一个接受该主题作为 css prop 的函数。

使用#

css function#
import { ThemeProvider } from '@emotion/react'

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

render(
  <ThemeProvider theme={theme}>
    <div css={theme => ({ color: theme.colors.primary })}>
      some other text
    </div>
  </ThemeProvider>
)
useTheme hook#
import { ThemeProvider, useTheme } from '@emotion/react'

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>
)

类型#

主题的类型需要在类型文件中声明,否则 TS 类型会报错。

types/emotion.d.ts

import "@emotion/react";

declare module "@emotion/react" {
  export interface Theme {
    colors: {
      layoutBodyBackground: string;
      headingColor: string;
      textColorSecondary: string;
      success: string;
      warning: string;
      error: string;
      primary: string;
      textColor: string;
    };
    fontSizes: {
      base: number;
    };
  }
}

tsconfig.json

{
  "compilerOptions": {
        "paths": {
          "*": [
            "types/*"
          ],
        },
    }
}

总结#

CSS in JS 的方案使用下来相比传统的 less、sass 等优点的确不少,而 emotion 的类似方案也有很多,对我感受最深的一点是,打包速度提升很多,没有冗余的 CSS 出现,但是对于习惯了 css 写法的人来说还是需要时间熟悉,总之,不妨一试。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。