乐趣区

React系列-从使用React了解Css的各种使用方案六

To define is to limit.
定义一样东西,就意味着限制了它。

——王尔德《道林·格雷的画像》

React 系列

React 系列 — 简单模拟语法(一)
React 系列 — Jsx, 合成事件与 Refs(二)
React 系列 — virtualdom diff 算法实现分析(三)
React 系列 — 从 Mixin 到 HOC 再到 HOOKS(四)
React 系列 — createElement, ReactElement 与 Component 部分源码解析(五)
React 系列 — 从使用 React 了解 Css 的各种使用方案(六)

前言

这里主要总结一下 CSS 是怎么一步步演化呈现在百花齐放的局面, 从用上 React 之后就更加多选择了, 我就举例一下我的理解和用过的方案, 还有一些了解过觉得还不错的方案. 本身没有推荐或贬低哪些的意思, 只是觉得可以满足需求的都是可以使用的方案.

CSS

用官方口吻来说就是

CSS 能够对网页中元素位置的排版进行像素级精确控制,支持几乎所有的字体字号样式,拥有对网页对象和模型样式编辑的能力。

具有以下特点:

  1. 丰富的样式定义

    CSS 提供了丰富的包括但不限于样式外观,文本字体, 背景属性, 边距形状等等的能力, 还有一些浏览器特有的属性定制.

  2. 易于使用修改

    可以将样式定义在 HTML 元素的 style 属性中,HTML 文档的 header 部分,也可以将样式声明在一个专门的 CSS 文件中。

    可以将相同样式的元素进行归类使用同一个样式进行定义,也可以将某个样式应用到所有同名的 HTML 标签中。

  3. 多页面应用

    CSS 样式表理论上不属于任何页面文件,在任何页面文件中都可以将其引用。这样就可以实现多个页面风格的统一。

  4. 层叠

    简单的说,层叠就是对一个元素多次设置同一个样式,后来定义的样式将对前面的样式设置进行重写。

  5. 体积减少

    在使用 HTML 定义页面效果的网站中,往往需要大量或重复的表格和 font 元素形成各种规格的文字样式,而将样式的声明单独放到 CSS 样式表中,可以大大的减小页面的体积,这样在加载页面时使用的时间也会大大的减少。另外,CSS 样式表的复用更大程序的缩减了页面的体积,减少下载的时间。

然后随着用户审美水平日益提高的标准和需求, 普通的样式已经不足以支撑起来了, 特别有些简单要求还会让开发绞尽脑汁用尽各种奇淫巧技来实现, 这种情况甚至会大大违背结构和样式分离的原则.

CSS3

作为 CSS 的升级版, 主要包括盒子模型、列表模块、超链接方式、语言模块、背景和边框、文字特效、多栏布局等模块.

CSS3 规范的一个新特点是被分为若干个相互独立的模块。一方面分成若干较小的模块较利于规范及时更新和发布,及时调整模块的内容,这些模块独立实现和发布,也为日后 CSS 的扩展奠定了基础。另外一方面,由于受支持设备和浏览器厂商的限制,没备或者厂商可以有选择的支持一部分模块,支持 CSS3 的一个子集,这样有利于 CSS3 的推广, 但不同浏览器在不同时段支持不同特性,这也让跨浏览器开发变得复杂, 当然截止到 2019, 主流浏览器已经基本支持了. 低端版本也能通过各种辅助插件完成.

提升优势:

  1. 减少开发成本与维护成本

    例如以前需要使用图片定位模拟圆角, 绝对定位 + 定时器模拟动画, 元素伪造滚动条样式等各种繁琐工作都可以省略掉了.

  2. 提高页面性能

    减少多余的标签嵌套以及图片的使用数量,意味着用户要下载的内容将会更少,页面加载也会更快, 能够减少用户访问 Web 站点时的 HTTP 请求数,这是提升页面加载速度的最佳方法之一

CSS3 的推广一方面增强了样式的展示, 一方面免去开发实现的额外工程, 但是本质上并没有减少开发的工作量, 因为有很多类似而不相同的样式还是需要手写出来. 现在的网页越来越大, 画面越来越丰富, 于是就应运而生出了 ——

预处理器

因为我都是使用 SCSS, 就以此举例, 但是其实语法和实现功能大致相同的.

预处理器是一种强化 CSS 的辅助工具,它在 CSS 语法的基础上增加了 变量 (variables)、嵌套 (nested rules)、混合 (mixins)、导入 (inline imports) 等高级功能,这些拓展令 CSS 更加强大与优雅, 有助于更好地组织管理样式文件,以及更高效地开发项目.

简单举例常用而强大的特性:

  1. 嵌套规则 (Nested Rules)

    允许将一套 CSS 样式嵌套进另一套样式中,内层的样式将它外层的选择器作为父选择器, 还有属性嵌套, 占位符选择器等等

  2. SassScript

    可以通过声明变量, 函数, 运算等抽取重复样式代码减少大量重复样板代码.

  3. 指令

    @import导入 SCSS 或 Sass 文件 ,@media 嵌套在 CSS 规则内,@extend继承样式等

  4. 控制指令

    运用各种判断循环实现控制样式

CSS 的瓶颈

上面都是前端开发的标配技能, 懂得自然都懂, 就不说太多了, 但是该碰到的坑自然都会遇到.

主要这么几个:

  1. 全局污染

    好的规范自然能够极大方便重用样式减少代码, 但是别说多人协作开发, 即使个人维护都可能碰到多种样式覆盖, 权重优先级混乱, 冗余重复属性过多, 于是各种内联样式,!important 强制最先等写法, 虽然也有其他方案如功能属性 class 拆分避免这种窘境, 但是增大开发的参与难度, 很难形成一套适用标准, 起码我觉得太繁琐了.

  2. 命名烦恼

    元素嵌套越深, 如果想命名语义化明确点就可能导致越来越长越复杂的命中规则, 而有一些一次性的样式也不得不遵循这种规则, 否则用内联样式替代

  3. 样式压缩

    因为样式对应到具体的引用属性 id,class, 选择器等, 这一块是没办法省略的, 嵌套越深入选择器前缀可能就越长, 例如 #A .B .C .D p{}这种

预处理器只是简化开发写法, 实际编译完成也就这个样, 本质上的问题没有得到解决.

CSS-in-JS

React 本身没有定义样式的主张, 而是用第三方库提供的 CSS-in-JS. 这里是一些实现的对比, 之所以会出现种类繁多的第三方库是因为它们实现功能的支持有所区别, 主要在

  1. Automatic Vendor Prefixing (自动添加前缀)
  2. Pseudo Classes (伪类)
  3. Media Queries (媒体查询)
  4. Styles As Object Literals (对象字面量样式)
  5. Extract CSS File (提取 CSS 文件)

例如 React 的官方示例原生 style 属性使用

style 属性接受具有驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串。这与 JavaScript DOM 的 style 属性一致,但是更高效,并且防止 XSS 安全漏洞。例如:

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {return <div style={divStyle}>Hello World!</div>;
}

注意:

  1. 样式不自动进行兼容。要支持旧版本的浏览器,您需要提供相应的样式属性
  2. React 会自动为某些内联样式的数字属性值附加一个“px”后缀。如果你想使用“px”以外的单位,请明确指定单位,并将该值指定为字符串
  3. 并不支持所有的 css,例如媒体查询,:before:nth-child 等 pseudo selectors

然后我们也能用一些其他的 CSS-in-JS 库解决, 我就拿比较多人使用支持功能比较完善的来举例一下, 下面方案都属于这种, 但是倾向和用法上有一定区别:

aphrodite

Framework-agnostic CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation.

Support for colocating your styles with your JavaScript component.

支持功能

  • 不强制和 React 搭配才能使用
  • 支持媒体查询无需 window.matchMedia
  • 支持伪选择器像 :hover, :active 等等, 不需要在组件中存储悬停或活动状态。:visited也能良好支持
  • 支持自动全局 @font-face 检测和插入
  • 在指定多个样式时,尊重优先顺序
  • 不需要 AST 转换
  • 只向 DOM 中注入渲染所需的确切样式。
  • 可以用于服务器渲染
  • 低依赖, 小(20k, 压缩后 6k)
  • 没有外部的 Css 文件生成
  • 自动添加前缀

示例

import React, {Component} from 'react';
import {StyleSheet, css} from 'aphrodite';

class App extends Component {render() {
        return <div>
            <span className={css(styles.red)}>
                This is red.
            </span>
            <span className={css(styles.hover)}>
                This turns red on hover.
            </span>
            <span className={css(styles.small)}>
                This turns red when the browser is less than 600px width.
            </span>
            <span className={css(styles.red, styles.blue)}>
                This is blue.
            </span>
            <span className={css(styles.blue, styles.small)}>
                This is blue and turns red when the browser is less than
                600px width.
            </span>
        </div>;
    }
}

const styles = StyleSheet.create({
    red: {backgroundColor: 'red'},

    blue: {backgroundColor: 'blue'},

    hover: {
        ':hover': {backgroundColor: 'red'}
    },

    small: {'@media (max-width: 600px)': {backgroundColor: 'red',}
    }
});

当然也很多人不太接受这种方式书写 Css, 还把 html 结构嵌套一堆 className={css()} 的写法.

styled-components

使用标记的模板文本和 CSS 的强大功能,样式组件允许您编写实际的 CSS 代码来样式化组件。它还删除了组件和样式之间的映射——将组件用作低级样式结构再简单不过了!

风格组件既兼容 React(用于 web),也兼容 React Native——这意味着它甚至是真正通用的应用程序的完美选择!

示例

import React from 'react';

import styled from 'styled-components';

// Create a <Title> react component that renders an <h1> which is
// centered, palevioletred and sized at 1.5em
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// Create a <Wrapper> react component that renders a <section> with
// some padding and a papayawhip background
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// Use them like any other React component – except they're styled!
<Wrapper>
  <Title>Hello World, this is my first styled component!</Title>
</Wrapper>

优点

  • 直接将样式组件化, 移除样式和组件的映射关系
  • 支持组件继承, 方便复用
  • 和原有 CSS 方式无障碍搭配使用

styled-jsx

这是我查资料的时候了解到的一种方案, 因为没有实际用过, 所以我只贴上它的功能示例

主要功能

  • 完全的 CSS 支持, no tradeoffs in power
  • 运行时大小仅为 3kb(从 12kb 压缩为 gzip)
  • 完全隔离: 选择器、动画、关键帧
  • 内置 CSS 浏览器前缀
  • 非常快速、最小和高效
  • 非服务器渲染时高效运行注入
  • 面向未来: 相当于服务器可渲染的 ”Shadow CSS”
  • 支持源映射
  • 支持动态样式和主题
  • 通过插件进行 CSS 预处理

示例

export default () => (
  <div>
    <p>only this paragraph will get the style :)</p>

    { /* you can include <Component />s here that include
         other <p>s that don't get unexpected styles! */ }

    <style jsx>{`
      p {color: red;}
    `}</style>
  </div>
)

CSS Module

这是我在学习 webpack 的 Css-loader 的时候了解到的一种方案, 现在在使用的方案

CSS Module 是一个 CSS 文件, 默认所有类和动画命名都限于局部范围. 所有资源 (url(...))@imports都在模块请求格式.

Css 模块会被编译成底层交换格式像 ICSS 或者Interoperable CSS, 但是书写格式和普通 CSS 文件一样.

当从 JS 模块导入 CSS 模块时,它导出一个具有从本地名称到全局名称的所有映射的对象。

CSS

/* style.css */
.className {color: green;}

JSX

import {PureComponent} from 'react';
import styles from './index.scss';

class Page2 extends PureComponent {constructor(props) {super(props)
  }

  render() {
    return (<div className={styles.className}>
      </div>
    );
  }
}

当然这不是只限于 React, 使用 webpack 或者其他打包工具的方式都能支持使用.

优点

模块化和可重用性

  • 没有冲突
  • 显性依赖
  • 不会污染全局
  • 可以配合预处理器使用
  • API 少, 开发单独 CSS 文件写法基本一致
退出移动版