乐趣区

关于vue.js:如何优雅地覆盖组件库样式

大家好,我是年年!组件库的款式笼罩不掉,这应该是很多前端在工作中遇到过的问题。明天从理论案例登程剖析起因,最初会给出在 React 和 Vue 我的项目中的最优解。

本文首发在我的公众号,订阅获取我的最新文章!

本文会讲清:

  1. React 中 CSS Module 的原理是什么?:global是做什么的?
  2. Vue 中 Scoped 的原理是什么?深度作用选择器是什么?

先不讲概念,间接从需要登程:我应用了 Antd 组件库来展现一个日历。


当初我想将以后日期下面的蓝色边框变成紫色。

公众号后盾回复「101」获取 React 版本的在线地址,回复「102」获取 Vue 版本的在线地址

能够试试你能不能实现。

不论是 React 还是 Vue,整个 Calendar 是被封装起来的,咱们没有方法在组件外简略加上 style/class 改变外部的款式。

import {Calendar} from 'antd';
...
<div className="myWrapper">
  <Calendar class="custom"/>
</div>

定位要笼罩的款式

首先用开发者工具定位对应的款式:.ant-picker-calendar-date-today,这就是咱们要批改的中央。

.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today {border-color: #1890ff;}

相熟 webpack 的人应该晓得,引入的 CSS 文件最终都会被 style-loader 解决。简略来说,它的作用就是把 CSS 文件打包,放在 style 标签内,最初塞进 HTML 中作为一个外部样式表。不论是组件库的款式还是咱们写的自定义款式都是这样解决的。

咱们要把组件库的款式先于自定义款式引入,这样自定义款式能力有更高的优先级。

批改源文件

间接改选件库的 CSS 源码是最简略粗犷的办法。关上你我的项目的 node_modules 文件夹,一层层点开,找到对应款式文件,依照需要批改即可。

集体我的项目这样解决的确可行,然而团队单干时,同步他人本地的 node_modules 就比拟麻烦,只能算一个 60 合成法。

全局 CSS 文件

之前提到,把本人写的的 CSS 文件放在组件库的款式前面,能够保障自定义有更高优先级。只有重写同名的款式,实践上就能实现笼罩组了。

但这样👇解决会发现并不起作用:

/* src/demo.css */
.ant-picker-calendar-date-today {border-color: purple; /* 笼罩为紫色 */}
// src/Demo.js

// 组件库的款式
import 'ant-design-vue/dist/antd.css'; 
// 自定义款式
import './demo.css'
import {Calendar} from 'antd';
...
<div className="myWrapper">
  <Calendar />
</div>
...

因为这里还波及 CSS 组合选择器的优先级。

根底的优先级应该不必赘述:!important> 内联款式 >ID 选择器 > 类选择器 > 标签选择器。(!important 这种 hack 会导致我的项目不好保护,不提倡应用)

在这个根底上还有五种组合选择器要对优先级分数做累计,以类选择器为例:

  1. 后辈选择器(空格):.A .B抉择.A 元素后的所有.B 元素,
  2. 子元素选择器(大于号):.A>.B抉择.A 元素的间接后辈中的.B 元素
  3. 相邻兄弟选择器(加号):.A+.B抉择.A 元素后紧邻的第一个兄弟.B 元素
  4. 后续兄弟选择器 (~ 号):.A~.B 抉择.A 元素后所有的兄弟.B 元素
  5. 交加选择器(连在一起):.A.B抉择本身同时领有.A 和.B 两个属性的元素

下面几个规定看着很简单,其实用的多的就是第一个后辈选择器,记住它就行。Antd 组件库用的就是它:

.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today {border-color: #1890ff;}

如果说一个类选择器优先级分数是 10 分,那三个造成的后辈选择器就是 30 分。

而自定义的款式👇只有 10 分,所以即便放在更前面引入,也不能胜利笼罩。

.ant-picker-calendar-date-today {border-color: purple; // 笼罩为紫色}

须要残缺重写整个选择器能力实现想要的成果。

这里补充一点,同样也是组合选择器,但并集选择器(逗号)优先级不累计:.A, .B抉择.A 或者.B 元素(能够是逗号 + 空格)

款式隔离 CSS Module 和 Scoped

下面咱们引入自定义的全局 CSS 文件,实现了款式的笼罩,然而这种解法只能给 80 分。因为在理论工作中,我的项目 Owner 通常不容许应用全局 CSS,这会造成款式净化:你定义了一个款式my_button,团队其他人凑巧也命名为my_button,这就造成款式抵触。

咱们须要给每个文件做款式隔离,就如同是给它一个命名空间。通常使 React 我的项目应用的是用的是 CSS Module,Vue 我的项目应用 Scoped 标记。

接下来会讲清两种款式隔离的原理,以及应用款式隔离时怎么笼罩组件库的款式。

React 的 CSS Module

首先来理解一下 CSS Module 的原理。它的应用很简略,在 CSS 文件加一个后缀.module,而后当做一个变量引入到 JS 文件中。

// src/Demo.js
import styles from './demo.module.css';
export default function Demo() {
  return (<div className={styles.myWrapper}>
      <Calendar />
    </div>
  );
}
/* src/demo.module.css */
.myWrapper {border: 5px solid black;}

被编译后👇,插入的样式表和元素的 class 属性都会加上一个哈希值作为命名空间。

<style>
.demo_myWrapper__Hd9Qg {border: 5px solid black;}
</style>
<div class="demo_myWrapper__Hd9Qg">
...
</div>

能够看到,本来的 CSS 选择器和 HTML 元素类名都从 myWrapper 变成了demo_myWrapper__Hd9Qg,后面加上了文件名,前面加上了哈希值,这样就能保障款式只在以后这个文件下失效了。

然而在这种款式隔离状况下,咱们本来用作笼罩的 CSS 也被加上了哈希值,就像下图这样,这时没有方法选中 UI 组件,笼罩也就不会胜利。

所以,React 给咱们提供了一个语法:global。它失效范畴内的款式会被当作全局 CSS。

具体应用如下,在 CSS 文件中,应用 :global 包裹心愿全局失效的款式

:global(.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today) {border-color:purple; /* 笼罩为紫色 */}

SCSS 或 SASS 中,还能够应用嵌套语法:

:global {
  .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today {border-color:purple;}
}

最初编译进去的代码如下:

/* 加上了哈希 */
.demo_myWrapper__Hd9Qg {border: 5px solid black;}
/* :global 作用域下都不会加上哈希 */
.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today {border-color:purple;}


借助 :global 语法,即便应用 CSS Module 进行款式隔离也能够如愿实现笼罩性能。

Vue 中的 Scoped

Vue 中也有相似的款式隔离性能,应用 Scoped 标记 CSS 局部,应用也很简略👇:

<style scoped>
.myWrapper{border: 5px solid black}
</style>
...
<div class="myWrapper" >
  <Calendar />
</div>
...

编译进去的代码如下👇:

<style>
.myWrapper[data-v-2fc5154c] {border: 5px solid black}
</style>
<div class="myWrapper" data-v-2fc5154c>
  ...
</div>

能够看到,它的原理和 CSS Module 不太一样,Vue 的 Scoped 会使 CSS 选择器后加上一个中括号。

这并不是 Vue 独创的语法,而是属性选择器。.myWrapper[data-v-2fc5154c]代表抉择领有 data-v-2fc5154c 这个属性的、同时是 myButton 类的 HTML 元素。只有这个文件外部的 HTML 元素才会被打上 data-v-2fc5154c 这个属性。其余文件的 HTML 元素即便是 myWrapper 类,这个款式也不会对他失效。

回到雷同的问题,如果 Vue 我的项目在应用了 Scoped 做款式隔离,咱们用于笼罩的款式也会加上属性选择器,然而 UI 组件外部的 HTML 元素都没有该属性👇。

所以 Vue 提供了一个相似的语法:深度作用选择器。

应用很简略,把要“浸透“进组件外部的款式 后面 加上>>>,作用域内的 CSS 款式都不会带上哈希值作为属性选择器。

<style scoped>
.myWrapper>>>
.ant-picker-calendar-full 
.ant-picker-panel 
.ant-picker-calendar-date-today{border-color:purple}
</style>
<template>
  <div class="myWrapper" >
    <Calendar  />
  </div>
</template>

编译后👇

<style>
.myWrapper[data-v-2fc5154c]
.ant-picker-calendar-full
.ant-picker-panel
/* 作用域内的 CSS 都没有带上属性选择器 */
.ant-picker-calendar-date-today {border-color:purple}
</style>

<div class="myWrapper" data-v-2fc5154c>
  <div class="ant-picker-calendar-full" data-v-2fc5154c>
    <div class="ant-picker-date-panel">
      <td class="ant-picker-cell-today"></td>
    </div>
  </div>
</div>

借助深度作用选择器,能够将要用于笼罩 CSS“浸透”进组件外部。

也能够将 >>> 写成 /deep/ 或者::v-deep

相较于 React 的:global,Vue 的深度作用选择器是一种更优良的计划,它必须要一个前导(也就是下面例子中的.myWrapper 选择器),前导依旧会被打上哈希值作为属性选择器,要浸透进去的款式实际上是作为它的子选择器,只在以后这个文件下失效,彻底防止造成全局净化。

结语

本文通过如何批改 UI 组件外部款式为切入点,剖析了几种解法。理解了组合选择器的优先级分数累加,以及在理论 React、Vue 我的项目用到的款式隔离计划——CSS Module 和 Scoped 的原理,最初是介绍了在款式隔离的状况下,如何应用:global 和深度作用选择器做款式笼罩。

如果这篇文章对你有帮忙,给我点个赞和在看吧~

你的激励是我创作的最大能源❤️

退出移动版