《React最佳实践系列》之css篇

https://user-gold-cdn.xitu.io/2019/9/17/16d3f137782dbd6a?w=1024&h=656&f=jpeg&s=141457

React中的css到底该怎么写?这一直是个饱受争论的话题。本文将结合React CSS的发展史,分别叙述CSS in js与CSS module两种风格中的最佳实践。css

行内样式

首先,facebook提出CSS in js的概念,这很奇怪。 咱们多年来所学的知识都在宣扬关注点分离的重要性,不该该将标记和css混在一块儿。可是React行内样式的提出,试图改变关注点分离这一律念。使其从技术分离向组件分离转变。html

经过CSS in js有如下两个优势:webpack

  1. React将组件做为应用架构的基础单元,经过组合组件来建立应用。
  2. 能够很方便的和逻辑进行交互。

好比这样(先忽略直接将style对象传入带来的性能问题)web

render(){
  return (
    <div style={{fontWeight: this.props.weight ? 'bold' : 'normal'}}>hello world</div>
  )
}
复制代码

行内样式也有缺点:设计模式

  1. 不支持伪选择器和伪元素
  2. 不支持媒体查询
  3. 不支持样式回退,由于js对象不支持两个同名属性
  4. 不支持动画
  5. 须要覆盖常规样式的时候,可能会须要!important来实现了
  6. 调试不方便
  7. 很关键的一点,若是是在服务端渲染的话,会使得页面体积变得很大

事实证实,虽然行内样式解决了目标问题,却引起了更多的问题。bash

CSS Module

若是你认为行内样式的方案不适合本身的团队,但仍然但愿尽可能紧密结合样式与组件,那么webpackcss-loader就帮了你大忙! 关于webpack和详细配置,本文再也不赘述。咱们看一下本文主要用到的loaderpluginantd

const HTMLWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HTMLWebpackPlugin()
  ]
}
复制代码

如上配置完之后,在终端启动项目后,就能够在网页中访问了。接下来咱们开始编写具体代码。架构

首先定义一个普通的css文件函数

.button {
  background-color: blue;
  color: #fff;
  padding: 10px 20px;
}
复制代码

而后在js文件中引入工具

import styles from './index.css'
复制代码

import语句会导入一个样式对象,其全部属性就是index.css中定义的类,这时候运行一下console,开发者工具会输出一个形如这样的对象。这个key是根据文件散列值和其余一些参数生成的。

{
  button: "_2wpxM3yizfwbWee6k0U1D4"
}
复制代码

接着,咱们能够用这个对象去设置按钮的className属性

const Button = () => (
  <button className={styles.button}>Click it</button>
)
复制代码

这样,按钮的样式就设置完成了。 咱们打开开发者工具能够看到,button的类名就是上面的_2wpxM3yizfwbWee6k0U1D4。若是查看页面头部,咱们还会发现相同的类名已经注入到页面中了。

css-loader容许你在js模块中导入css文件,而且启用modules标记时,全部类名都只做用于导入它们的模块。最后,style-loader接收css-loader转换的结果,并将样式注入到页面头部。

这种用法很是强大,由于咱们拥有了css的完整能力及表现性,又结合了局部做用域类名与显示依赖的优势。咱们还能够像这样配置localIdentName参数,解决调试时信息不清晰的问题。

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        { loader: 'style-loader' },
        {
          loader: 'css-loader',
          options: {
            modules: true,
            localIdentName: '[path][name]__[local]--[hash:base64:5]'
          }
        }
      ]
    }
  ]
}
复制代码

生产环境不须要这样的类名,更注重性能,所以咱们想要更简短的类名和散列值。 咱们能够在生产环境下使用MiniCssExtractPlugin将样式提取到单独的css文件,并将其放入CDN,从而得到更好的性能。

css-loader还支持一些关键词。

第一个就是global关键词。给任何类添加:global前缀,意味着请求CSS模块不要为当前选择器加上局部做用域。

例如:

:global .button {
  ...
}
复制代码

这样,你能够应用不须要局部做用域的样式。好比第三方组件。

第二个就是composes,有了它,就能够从同个文件或外部依赖中引用类名,将其它类的全部样式应用于一个元素。

例如:

.text-red{
  color: red;
}

.button {
  composes: text-red;
  background-color: blue;
  color: #fff;
  padding: 10px 20px;
}
复制代码

最终,button类的全部规则以及composes声明的全部规则都能做用于元素。

这个特性很是强大,并且原理很巧妙。你可能觉得它和SASS@extend方法同样,只是将组合类复制到引用它们的位置,其实不是这样。简单来说,全部组合类名都是逐个应用到DOM中的组件上。

以咱们的示例来讲,代码以下:

<button class="_2wpxM3yizfwbWee6k0U1D4 Sf8w9cFdQXdRV_i9dgcOq">Click it</button>
复制代码

注入页面的css以下所示:

.Sf8w9cFdQXdRV_i9dgcOq {
  color: red;
}

._2wpxM3yizfwbWee6k0U1D4 {
  background-color: blue;
  color: #fff;
  padding: 10px 20px;
}
复制代码

原子级CSS

原子级CSS又称函数式CSS,是CSS的一种使用方式,即每一个类只有一条规则。

例如,能够用一个类来设置底部外边距为0:

.mb0 {
  margin-bottom: 0;
}
复制代码

而后用另外一个类设置font-weight为bold

.fwb {
  font-weight: bold;
}
复制代码

而后将这些原子类用在元素上:

<span class="mb0 fwb">Hello World</span>
复制代码

这种技巧存在争议,但很高效。一方面,类是在CSS文件中定义,却在视图层组合,每次修改元素的样式都要同时修改两个地方;另外一方面,它能够超快地搭建页面。

其实,只要全部基本规则都定好,将这些类应用于元素或者用它们生成新的样式都很是快,这是一大优势。其次,使用原子级CSS能够控制css文件的大小,由于建立新组建时能够复用已有类的样式,不须要编写新样式,这对性能颇有好处。

原子级CSS Module

咱们能够将CSS Module与原子级CSS结合起来使用。

查看如下示例:

.title {
  composes: mb0 fwb;
}

<span class="title">Hello World</span>
复制代码

这种作法很是好,由于样式逻辑仍然保留在CSS中,同时CSS模块利用composes将全部单个类聚合到一个类中。

上述代码的渲染结果以下所示:

<span class="title--3JCJR mb0--21SyP fwb--1JRhZ">Hello World</span>
复制代码

此处的titlemb0以及fwb都是自动加到元素上的。而且它们都只做用于局部。这样,咱们就用上了CSS Module的全部优点。

styled-components

最后,让咱们看一下CSS in js中的王者——styled-components。 这个库能够说考虑到了其余组件样式库遇到的全部问题。

在安装styled-components以后,咱们能够这样使用它:

import styled from 'styled-components'

const Button = styled.button` background-color: blue; color: #fff; padding: 10px 20px; `
复制代码

能够看到它使用了模板字符串,这意味着它能够用js的所有能力为元素添加样式。 这种看似奇怪的语法会返回普通的React组件Button,它渲染了一个按钮元素,并加上了模板中定义的样式。先建立惟一的类名,再将它加到元素上,最后向页面文档头部注入相应的样式。至此,样式生效了。

渲染的组件以下所示:

<button class="kYvFOg">Click it</button>
复制代码

页面上添加的样式以下:

.kYvFOg {
  background-color: blue;
  color: #fff;
  padding: 10px 20px;
}
复制代码

这个库的优势以下在于支持几乎全部的css特性。好比,它支持SASS风格的伪类语法:

const Button = styled.button` background-color: blue; color: #fff; padding: 10px 20px; &:hover { background-color: #fff; color: blue; } `
复制代码

它也支持媒体查询:

const Button = styled.button` background-color: blue; color: #fff; padding: 10px 20px; &:hover { background-color: #fff; color: blue; } @media (max-width: 480px) { padding: 10px 10px; } `
复制代码

它还能够很方便地覆盖样式,并设置不一样属性来屡次复用该组件。当你使用了形如antd之类的UI库,并但愿自定义一些样式时,这个优势会很是明显。

结尾

CSS in or not in js ? this is a question.

我以为没有绝对的最佳实践,只有在工程迭代的过程当中,不停的找寻最适合工程、最适合团队的方案,这才是最明智的选择。

参考文献:

  • 《React设计模式与最佳实践》第七章