Habla sobre el CSS-in-JS que entiendo (1)

Estoy participando en el "Proyecto Nuggets · Sail". En este artículo, principalmente quiero compartir con ustedes mi comprensión de CSS-in-JS.

1. Puntos débiles del CSS tradicional

CSS ha sido uno de los núcleos de la tecnología web desde el principio, y CSS se ha vuelto cada vez más estandarizado y más poderoso en los últimos años. Sin embargo, CSS (estandarizado hasta ahora) aún no tiene algunos de los conocimientos y capacidades de dominio necesarios para el desarrollo moderno de componentes front-end , por lo que se necesitan otras tecnologías para complementarlo.

Estos conocimientos y habilidades incluyen principalmente cuatro aspectos:

  1. El alcance de los estilos de componente debe controlarse a nivel de componente;
  2. Los estilos de componente y los componentes deben establecer una asociación más fuerte en el código fuente y los niveles de compilación;
  3. Los estilos de los componentes deben responder a los cambios de datos de los componentes;
  4. Los estilos de componentes deben tener la capacidad de reutilizarse y expandirse en unidades de componentes.

Por lo tanto, en el proceso de desarrollo de componentes front-end modernos, han surgido muchas soluciones en la comunidad, divididas principalmente en las siguientes categorías:

1. Convención de nomenclatura

Para controlar el alcance de los estilos de componente a nivel de componente, la solución más simple y directa que podemos pensar es, por supuesto, formular especificaciones de definición de nombre de clase. En la actualidad, las especificaciones BEM se usan comúnmente en la industria, como

// style.css
.btn {
  background-color: white;
}
.btn__icon {
  color: black;
}

import './stype.css'
const TButton = props => {
  return (
    <button className="btn">
      <icon className="btn__icon" name={props.incon}></icon>
    </button>
  )
}

Al garantizar artificialmente el aislamiento de estilo de los componentes a través de convenciones de nomenclatura, este método tendrá los siguientes problemas:

  1. Muy dependiente de la normalización del código por parte del equipo de desarrollo.
  2. Los estilos de los componentes no tienen forma de responder a los cambios en los datos de los componentes.
  3. No proporciona capacidades de reutilización muy detalladas.

Por lo tanto, este método no se usa mucho en las aplicaciones empresariales front-end modernas a gran escala . Este método es más adecuado para el desarrollo de bibliotecas de componentes básicos . Las razones principales son:

  1. Usando la biblioteca de componentes desarrollada por clase, el lado comercial puede cubrirse fácilmente con el estilo del componente
  2. La biblioteca de componentes básicos generalmente la desarrolla un equipo dedicado, y la convención de nomenclatura se puede unificar
  3. El uso de la clase más básica puede reducir efectivamente el tamaño de la biblioteca de componentes

2, estilo en línea

Tanto Vue como React en realidad proporcionan diferentes implementaciones de estilos dinámicos.Tome la sintaxis JSX de React como ejemplo:

const App = props => {
  return (
    <div style={{color: "red"}}>123</div>
  )
}

En comparación con la implementación de convenciones de nomenclatura, Inline Styling proporciona una solución muy directa y simple para responder a los cambios de estado en los componentes. Al mismo tiempo, también podemos implementar la reutilización o extensión de estilos extrayendo algunos estilos como variables.

但是这种方式如果在央视属性过多的情况下会让代码显得非常混乱,因此 Inline Styling 往往用于元素部分属性调整的情况。

3、CSS Modules

CSS Module 是目前使用的比较多的解决方案,它不是将 CSS 改造成编程语言,而是在 CSS 的基础上通过构建工具对其进行了扩展,针对前面我们提到的 CSS 面对现代前端组件化开发能力的不足给出了自己的解决方案。

3.1、组件级的隔离:局部作用域

CSS Modules 实现局部作用域的核心是使用独一无二的 class,以下面 React 组件为例:

// App.jsx
import React from 'react';
import style from './App.css';

export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};
/*  App.css */
.title {
  color: red;
}

构建工具会将类名 style.title 编译成一个哈希字符串。

<h1 class="_3zyde4l1yATCOkgn-DBWEL">
  Hello World
</h1>

App.css 也会同时被编译。

._3zyde4l1yATCOkgn-DBWEL {
  color: red;
}

这样一来,这个类名就变成独一无二了,只对App组件有效。

大家可以看到 CSS Modules 实现的核心就在构建工具处理的这个阶段,我们以 webpack 为例,使用 webpack 的 css-loader 可以在打包项目的时候指定该样式的 scope,例如

// webpack config
module.exports = {
  module: {
    loaders: [
      { 
        test: /.css$/, 
        loader: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 
      },
    ]
  },
  ...
}

这里的 css-loader 会将类名 style.title 转变成 title__app__hash 的格式。

3.2、更灵活的限制:全局作用域

CSS Modules 允许使用 :global(.className) 的语法,声明一个全局规则。凡是这样声明的 class,都不会被编译成哈希字符串。举个例子:

/* App.css */
.title {
  color: red;
}

:global(.title) {
  color: green;
}
// APP.jsx
import React from 'react';
import styles from './App.css';

export default () => {
  return (
    // 注意这里的类名写法是普通写法
    <h1 className="title">
      Hello World
    </h1>
  );
};

3.3、样式的复用:class 的继承

在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为"组合(composition)"。

/* App.css */
.className {
  background-color: blue;
}

.title {
  composes: className;
  color: red;
}
import React from 'react';
import style from './App.css';

export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};

这种情况下 App.css 文件会被编译成如下内容:

._2DHwuiHWMnKTOYG45T0x34 {
  color: red;
}

._10B-buq6_BEOTOl9urIjf8 {
  background-color: blue;
}

而 h1 元素会被编译成 <h1 class="_2DHwuiHWMnKTOYG45T0x34 _10B-buq6_BEOTOl9urIjf8">

3.4、变量的引入

CSS Modules 支持使用变量,不过需要安装 postcss-loaderpostcss-modules-values

// webpack config
const values = require('postcss-modules-values');

module.exports = {
  module: {
    loaders: [
      { 
        test: /.css$/, 
        loader: 'css-loader?modules!postcss-loader
      },
    ]
  },
  postcss: [
    values
  ]
  ...
}

接着,在 colors.css 里面定义变量。

@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;

App.css 可以引用这些变量。

3.5 总结

CSS Modules 的这种做法非常类似 Angular 与 Vue 对样式的封装方案,其核心是以 CSS 文件模块为单元,将模块内的选择器附上特殊的哈希字符串,以实现样式的局部作用域。在 React 项目中我们通过引入相关打包工具配置同样可以实现相同的效果,对于大部分应用开发场景已经可以完全支持。

二、CSS-in-JS

CSS-in-JS 顾名思义是在 JS 中直接编写 CSS 的技术,也是 React 官方推荐的编写 CSS 的方案,在 github.com/MicheleBert… 这个代码仓库中我们可以看到 CSS-in-JS 相关的 package 已经有60多个了。这里我们主要介绍 emotion ,这个框架比起其他框架更注重开发者体验(Developer Experience),功能相对完整,也比其他一些专注于用 JS、TS 语法写样式的框架更“CSS”一些。

1、基本使用

import { css } from '@emotion/react'

const color = 'white'

render(
  <div
    css={css`
      padding: 32px;
      background-color: hotpink;
      font-size: 24px;
      border-radius: 4px;
      &:hover {
        color: ${color};
      }
    `}
  >
    Hover to change color.
  </div>
)

可以看到 emotion 对于组件内样式隔离、数据变量都有着比较好的支持,而且对于伪类选择器等 CSS 属性也有着比较好的支持,除此之外,比较常用还有子元素选择器:

import { css } from '@emotion/react'

render(
  <div
    css={css`
      padding: 32px;
      background-color: hotpink;
      & > span {
       background-color: blue;
      }
    `}
  >
    Hover to change color.
  </div>
)

子选择器 > 对于 KanbanColumn 组件是必要的。如果去掉 > ,仅保留空格,上面三个子选择器就变成了后代选择器,无论在 DOM 树中的深度如何,只要是子孙元素中的 span 元素就会被应用上面的样式,这就会污染传入的 children 子组件的样式,偏离了我们样式隔离的目标。

2、样式的组合与复用

最简单直接的样式复用方式当然是声明一个值为 css 函数执行结果的常量,然后可以在不同组件中赋给 HTML 元素的 css 属性:

import { css } from '@emotion/react'

const commonStyles = css`
  padding: 32px;
  background-color: hotpink;
  & > span {
   background-color: blue;
  }
`}

render(
  <div
    css={commonStyles}
  >
    Hover to change color.
  </div>
)

在此基础上我们也可以选择更加灵活的样式组合:

import { css } from '@emotion/react'

const commonStyles = css`
  padding: 32px;
  background-color: hotpink;
  & > span {
   background-color: blue;
  }
`}

const color = 'white'

render(
  <div
    css={
      css`
        ${commonStyles}
        &:hover {
          color: ${color};
        }
      `
    }
  >
    Hover to change color.
  </div>
)

除此之外如果要组合两个或更多 css 函数返回值的变量,还可以用数组的写法,如果其中有重复的 CSS 属性(如 color: redcolor: blue),那么后面的会覆盖前面的:

<div css={[style1, style2, style3]}>...</div>

3、基本原理

为了说明 emotion 在背后做了一些什么我们先用 emotion 写一个基本组件:

import React, { useState } from 'react'
import { css } from '@emotion/react'

const KanbanBoard = ({ children }) => (
  <main css={css`
    flex: 10;
    display: flex;
    flex-direction: row;
    gap: 1rem;
    margin: 0 1rem 1rem;
 `}>{children}</main>
)

把开发者工具切换到检查器页签,可以看到标签的 class 属性值变成了一个貌似没有意义的类名 css-130tiw0-KanbanBoard,而这个 CSS 类是在 HTML 文档的里动态插入的

类名中的 130tiw0 是个哈希值,用来保证类名在不同组件间的唯一性,这自然就避免了一个组件的样式污染另一个组件。你不妨将类样式代码格式化,会得到如下片段:

.css-130tiw0-KanbanBoard {
  -webkit-flex: 10; 
  -ms-flex: 10;
  flex: 10; 
  display: -webkit-box; 
  display: -webkit-flex; 
  display: -ms-flexbox; 
  display: flex; 
  -webkit-flex-direction: row; 
  -ms-flex-direction: row; 
  flex-direction: row; 
  gap: 1rem; 
  margin: 0 1rem 1rem;
}

貌似比一开始手写的代码增加了几行?是的,增加的这几行中,-webkit-、 -ms- 这样的前缀称作Vendor Prefix 浏览器引擎前缀,浏览器厂商用这种方式来引入尚未标准化的、实验性的 CSS 属性或属性值。为了提高浏览器兼容性,emotion 框架会自动为较新的 CSS 标准加入带有前缀的副本,不认识这些前缀的浏览器会忽略这些副本,而老版本浏览器会各取所需,这样只需按最新标准编写一次 CSS,就可以自动支持新老浏览器。

这儿 emotion 实际上是做了以下三个事情:

  1. 将样式写入模板字符串,并将其作为参数传入 css 方法。
  2. 根据模板字符串生成 class 名,并填入组件的 class="xxxx" 中。
  3. 将生成的 class 名以及 class 内容放到 style 标签中,然后放到 html 文件的 head 中。

参考资料

CSS Modules 用法教程 - 阮一峰的网络日志

github.com/camsong/blo…

juejin.cn/post/707120…

Supongo que te gusta

Origin juejin.im/post/7248168880573235260
Recomendado
Clasificación