本文介绍如何在 React 项目中使用 CSS in JS 方案:emotion。
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 类型已经被完全重写。#
- 减少使用 emotion 时的构建时间,尤其是在大型项目中。
- 在许多情况下,不再需要为 emotion 组件手动指定通用参数
- 作为 props 的联合类型得到了更好的支持,应该正确推断
- 限制了 css 函数以防止传递无效的类型
- styled 的通用参数已更改,如果您指定 ComponentType,则需要删除该通用参数
- 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 写法的人来说还是需要时间熟悉,总之,不妨一试。