谈谈 styled-components

早在刚开始接触 React 的时候就有听闻过 css in js 的理念,中间也简单使用过 styled-components,但用到最后总因为各种原因放弃。这次刚好心血来潮又想重新试试,同时记录一下从个人角度来看 styled-components 的优缺点,以便将来选型时作为参考之用。

基于 create-react-app 搭建的 TypeScript 项目,至于如何加入 styled-components,这里不赘述,按照文档很简单就可以完成了。以下简称为 sc

使用

简单使用方法如下:

import styled from 'styled-components'
const Title = styled.h1`
  font-size: 1.5em;
`;

render(
  <Title>
    Hello World!
  </Title>
);

最终编译出来的是一个独特的 hash 值的样式名,这可以避免我们样式名称被覆盖,而在此之前我们往往需要配置 css modules, 如果是 create react app 创建的项目,往往会用 .module.css 来表示 css 模块。而 sc 则自带了这个特性,我觉得这是他的第一个优点。

styled-components 可以传 props。

import styled from 'styled-components'

const Title = styled.h1<{isRed: boolean}>`
  font-size: 1.5em;
	color: ${props => props.isRed ? 'red' : 'black'}
`;

const isRed = ...

render(
  <Title isRed={isRed}>
    Hello World!
  </Title>
);

而在使用 styled-components 之前,我们要做到这个效果往往是在 style 里处理,或者是通过样式名来控制

// 在 style 里处理,不方便复用
render(
  <h1 style={{ color: isRed ? 'red' : 'black' }}>
    Hello World!
  </h1>
);

// 通过样式名控制较为繁琐
.red { color: 'red' }
.black { color: 'black' }

render(
  <h1 className={isRed ? 'red' : 'black'}>
    Hello World!
  </h1>
);

这是 styled-components 的第二个优点。

有个场景比较经常出现在维护旧代码的时候,往往有的组件不需要了,我们可以简单移除掉,但是这个组件所使用的样式名却不敢轻易删掉,因为鬼知道这个样式是不是别的地方用到了。而这个则是 sc 中样式组件则不存在这个问题,所以这是第三个优点:便于维护。

以上是我觉得感知比较强烈的优点,当然还有一些别的优点这里就不说了,具体可以查看文档。

说完了优点,说说开发时遇到的缺点吧

第一个就是由于 sc 实际上就是组件,而这个往往会让我分不清哪些是样式组件,哪些是 React 组件,就是显得不够直观。

第二个是有一些组件,实际上只是一行样式就解决了,那么每次都需要重新写一个 sc 组件,反而显得繁琐。官方是推荐可以使用 css prop 来解决

import { css } from 'styled-components/macro'

render(
  <h1 css={css`font-size: 1.5em;`}>
    Hello World!
  </h1>
);

这个功能需要有babel-plugin-styled-components 或者 babel-plugin-macro 的支持,create react app 默认支持了,详情看文档

如果是 Typescript,由于原先的组件上并没有 css 这个属性,则需要额外在全局引入

import {} from 'styled-components/cssprop'

文档中提到 css prop 实际上也是将组件转为样式组件,而如果是在 typescript 中需要传递自定义 prop,我不知道该如何定义类型?

import { css } from 'styled-components/macro'
const Title = css`
	font-size: 1.5em;
	color: {props => props.color}
`

render(
  // 此时将提示 color 类型错误云云。。
  <h1 css={Title} color="red">
    Hello World!
  </h1>
);

第三个是样式复用,即如果我有相同的样式想要用在不同的组件上,sc 也是推荐使用 css props,而 css props 的体验是在是不敢说很好。

就我个人而言目前主要是这三点用着不爽。

原理

那么 sc 为什么可以通过 styled来给组件添加样式的呢?

es6 提供了 `` 作为模板字符串,我们通常的用法

const name = `${firstName}${secondName}`

而如果 ``是跟随在一个函数时,则该函数将被调用来处理这个模板字符串,这个称为标签模板

console.log`hi`
// 等同于
console.log('hi')

关于这部分可以查阅 MDN 相关文档

styled.div`
	color: red
`
// 等同于
styled.div(['\n  color: red;\n'])

在编译后的代码中可以看到一段如下代码,不难猜测出 Object(i.a) 就是 styled.div

var n = Object(i.a)(['\n  color: red;\n'])

styled.div等同于 styled('div'),因而 styled 可以看作一个高阶组件

const StyledComponent = styled(Component)(styles)

后续

为了解决一两行代码也要用 css prop 的情况,我引入了 tailwindcss,这个 css 框架中的原子性类名理念非常契合我的问题。我觉得这两个框架搭配写起来还是比较顺畅的。