乐趣区

关于vue.js:Vue-SFC-Style-CSS-变量注入详解新版

CSS 变量能够跟 JavaScript 更好的通信,CSS 变量是运行时。

通过本文你会意识并了解以下概念:

  1. SFC Style – 单文件组件的款式;
  2. 原生 CSS 变量 – CSS 作者定义的标准规范;
  3. SFC Style Variables 提案(旧版);
  4. SFC style CSS variable injection(新版);
  5. Vue3 中的应用 CSS 变量注入以及应用原生 CSS 变量;
  6. 变量注入的背地原理;
  7. CSS 变量注入的劣势。

在 SFC Style Variables 提案中介绍到,Vue SFC 款式提供了简略的 CSS 组合和封装,但它是纯动态的 — 这意味着到目前为止咱们还没有能力在运行时依据组件的状态动静更新款式。

当初大多数古代浏览器都反对原生 CSS 变量,咱们能够利用它轻松连贯组件的状态和款式。

SFC Style 简略介绍

Vue 单文件组件 (SFC) 标准 中介绍到,.vue 文件是一个自定义的文件类型,用类 HTML 语法形容一个 Vue 组件。每个 .vue 文件蕴含三种类型的顶级语言块 <template><script><style>,还容许增加可选的自定义块。

Style 语言块:

  • 默认匹配:/\.css$/
  • 一个 .vue 文件能够蕴含多个 <style> 标签。

    <style> 标签能够有 scoped 或者 module 属性 (查看 scoped CSS 和 CSS Modules) 以帮忙你将款式封装到以后组件。具备不同封装模式的多个 <style> 标签能够在同一个组件中混合应用。

  • 任何匹配 .css 文件 (或通过它的 lang 个性指定的扩展名) 的 webpack 规定都将会使用到这个 <style> 块的内容中。

vue-loader 会解析文件,提取语言块,如有必要会通过其它 loader 解决,最初将他们组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。

Stlye 模块中能够应用 lang 属性,指定 CSS 预处理语言(sass、less、stylus),如下:

/* lang 属性指定扩展名 */
<style lang="sass">
  /* write Sass! 
</style>

还能够应用 src 属性,引入内部款式资源:

<style src="./style.css"></style>

/* 从 npm 依赖中引入资源 */
<style src="todomvc-app-css/index.css">

更多 Vue 单文件组件 (SFC) 标准 介绍。

原生 CSS 变量

CSS 变量是 CSS 作者定义的标准规范。

CSS 变量又称为 CSS 自定义属性,它蕴含的值能够在整个文档中重复使用,示例如下:

/* :root 伪类代表 HTML 文档的根元素,是寄存自定义属性的最佳地位。*/
:root {
  /* --text-color 为自定义属性 */
  --text-color: #000000; 
}  

p {/* 应用时,须要应用 var() 函数并传入自定义属性。*/
  color: var(--text-color);
  font-size: 16px;
}

h1 {color: var( --text-color);
  font-size: 42px;
}

定义及应用 CSS 自定义属性

在应用 CSS 自定义属性之前,咱们须要先申明自定义属性,属性名须要以两个减号(--)开始,属性值则能够是任何无效的 CSS 值。和其余属性一样,自定义属性也是写在规定集之内的,如下:

element {--main-bg-color: brown;}

规定集所指定的选择器,定义了自定义属性的可见作用域。通常的最佳实际是定义在根伪类 :root 下,这样就能够在 HTML 文档的任何中央拜访到它了。

:root {--main-bg-color: brown;}

留神:自定义属性名是大小写敏感的,--my-color--My-color 会被认为是两个不同的自定义属性。

如前所述,应用一个局部变量时用 var()) 函数包裹以示意一个非法的属性值:

element {background-color: var(--main-bg-color);
}
:root {--main-bg-color: brown;}

.one {
  color: white;
  background-color: var(--main-bg-color);
  margin: 10px;
  width: 50px;
  height: 50px;
  display: inline-block;
}

对于 CSS 自定义属性的继承性、备用值等更多介绍,请参考应用 CSS 自定义属性(变量)。

SFC 提案

sfc-style-variables 次要概述中指出,此提案 反对单文件组件状态驱动的 CSS 变量注入到单文件组件款式中

<template>
  <div class="text">hello</div>
</template>

<script>
export default {data() {
    return {color: 'red'}
  }
}
</script>

<style vars="{color}">
.text {color: var(--color);
}
</style>

为什么应用状态驱动的 CSS 变量

因为 Vue SFC 款式提供了简略的 CSS 搭配和封装,但它是纯动态的 — 这意味着到目前为止 咱们还没有能力在运行时依据组件的状态动静更新款式

当初大多数古代浏览器都反对原生 CSS 变量,咱们能够利用它轻松连贯组件的状态和款式。

对于提案设计

在此提案设计中(旧版),Vue SFC Style 反对 vars 绑定,它承受一个 key/values 表达式作为 CSS 变量注入。它与 <template> 中的表达式在雷同的上下文中进行计算。

变量将作为内联款式利用于组件的根元素。在下面的示例中,给定一个值为 {color:'red'}vars 绑定,出现的 HTML 将是:

<div style="--color:red" class="text">hello</div>

scoped 模式下

当在 scoped 模式下应用,须要确保 CSS 变量不会透露到后辈组件或不小心将 CSS 变量遮蔽到 DOM 树的更高层。利用的 CSS 变量将以组件的作用域 ID 为前缀:

<div style="--6b53742-color:red" class="text">hello</div>

请留神,当 scoped 和 vars 同时存在时,所有 CSS 变量都被视为本地变量。

在这种状况下,应用全局 CSS 变量,须要应用 global: 前缀:

<style scoped vars="{color}">
h1 {color: var(--color);
  font-size: var(--global:fontSize);
}
</style>

旧版提案设计的弊病

  1. 须要手动申明 vars 以公开能够应用的变量。
  2. 没有显著的视觉暗示变量被注入和响应。
  3. scoped/non-scoped 模式下的不同行为。
  4. non-scoped 模式下,CSS 变量会透露到子组件中。
  5. scoped 模式下,应用在组件内部申明的一般 CSS 变量须要 global: 前缀。(通常 CSS 变量用法最好在组件内外放弃雷同)

新版提案的改良

为了解决上述问题,新版改良用法如下:

<template>
  <div class="text">hello</div>
</template>

<script>
  export default {data() {
      return {
        color: 'red',
        font: {size: '2em'}
      }
    }
</script>

<style>
  .text {color: v-bind(color);

    /* expressions (wrap in quotes) */
    font-size: v-bind('font.size');
  }
</style>
  • 无需明确申明哪些属性被注入为 CSS 变量(从 CSS 中的应用 v-bind() 进行推断);
  • 反应变量的视觉差异更显著;
  • scoped/non-scoped 模式下的雷同行为;
  • 不会透露到子组件中;
  • 一般 CSS 变量的应用不受影响。

在 Vue3 中应用

示例不具体介绍,请联合正文进行了解。

示例中蕴含:

  1. 应用了新的 script setup ;
  2. 原生 CSS 变量的定义以及应用 var()
  3. CSS 变量注入的应用 v-bind
  4. 在运行时,响应式扭转 CSS 款式;
  5. 举荐格调。
<template>
  <div class="root">
      <span class="test" @click="changeColor"> Vue GoldenLayout</span>
    <div>
</template>

// script setup
<script lang="ts" setup>
import {defineComponent, reactive} from "vue";

// 将 css 款式独自抽离
import {style} from "../styles/vlogo";
const css = reactive({...style});

// 点击,批改组件的款式
const changeColor = () => (css.color = "yellow");
</script>

<style>
.root {
  // 原生 css 变量的定义
  --custom-color: v-bind("css.background");
}
.test {
  // 应用 v-bind 进行状态驱动 
  color: v-bind("css.color");
  // 应用 var()background: var(--custom-color);
}
</style>

最终出现的成果以及编译后的 HTML、CSS:

注:在 vue3 中应用 CSS 变量注入,举荐的格调是将 CSS 款式独自抽离进来

变量注入的背地原理

咱们在上文 SFC Style 简略介绍 中理解到,vue-loader 会解析 .vue 文件,并提取语言块,如有必要会通过其它 loader 解决,最初将他们组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。

如果在 <style> 中通过 lang 属性,应用其余 CSS 预处理语言(lesssass)等,则会匹配构建工具(webpack、vite)所配置的 loader 进行特定解决。

/*packages/compiler-sfc/sfc/stylePreprocessors.ts */
// .scss/.sass processor
const scss: StylePreprocessor = (source, map, options, load = require) => {...}
const sass: StylePreprocessor = (source, map, options, load) => {...}

// .less
const less: StylePreprocessor = (source, map, options, load = require) => {...}

// .styl
const styl: StylePreprocessor = (source, map, options, load = require) => {...}

Vue3 SFC Style 中的局部编译次要是由 postcss 实现的,在 Vue 源码中对应着 packages/compiler-sfc/sfc/compileStyle.ts 中的 doCompileStyle() 办法。

这里,咱们看一下其针对 <style> 动静变量注入的编译解决,对应的代码(省略代码):

export function doCompileStyle(options: SFCAsyncStyleCompileOptions): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
  const {
    ...
    id,
    ...
  } = options
  ...
  const plugins = (postcssPlugins || []).slice()
  plugins.unshift(cssVarsPlugin({ id: shortId, isProd}))
  ...
}

能够看到,在应用 postcss 编译 <style> 之前会退出 cssVarsPlugin 插件,并给 cssVarsPlugin 传入 shortId(即 scopedId 替换掉 data-v 内的后果)和 isProd(是否处于生产环境)。

cssVarsPlugin 则是应用了 postcss 插件提供的 Declaration 办法,来拜访 <style> 中申明的所有 CSS 属性的值,每次拜访通过正则来匹配 v-bind 指令的内容,而后再应用 replace() 办法将该属性值替换为 var(--xxxx-xx),体现在下面这个例子会是这样:

cssVarsPlugin 插件的定义:

const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g
const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {const { id, isProd} = opts!
  return {
    postcssPlugin: 'vue-sfc-vars',
    Declaration(decl) {
      // rewrite CSS variables
      if (cssVarRE.test(decl.value)) {decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {return `var(--${genVarName(id, $1 || $2 || $3, isProd)})`
        })
      }
    }
  }
}

这里 CSS var() 的变量名(--之后的内容)是由 genVarName() 办法生成,它会依据 isProdtruefalse 生成不同的值:

function genVarName(id: string, raw: string, isProd: boolean): string {if (isProd) {return hash(id + raw)
  } else {return `${id}-${raw.replace(/([^\w-])/g, '_')}`
  }
}

以上只是对 SFC Style 块的相干解决的局部解读,对于更残缺的源码解析请参考 Vue 3 的 SFC Style CSS Variable Injection 提案实现的背地。

CSS 变量注入的劣势

  1. 主题 – 通过响应式的全局款式,进行主题变更。(参考 Naive UI)。
  2. 同其余 CSS 预处理语言(Less、Sass 等)相比,免于装置,不必配置 loader
  3. 联合响应式个性,能够很好的模块化,不必导出 CSS 款式文件。

最初,想吐槽的一点是,从 Vue2 到 Vue3 的转变是思维上的转变,Vue 3 给了用户太多抉择,让用户莫衷一是。

参考:

  1. 单文件组件(SFC)标准
  2. 应用 CSS 自定义属性(变量)
  3. Vue 3 的 SFC Style CSS Variable Injection 提案实现的背地
退出移动版