讲解

数据变了,样式跟着变——高亮当前 tab、给未读消息加红点、给错误输入框描红边,这些都要动态绑定 class 和 style。Vue 给这两个属性做了语法增强:除了传字符串,还可以传对象和数组。

对象语法是条件开关::class="{ active: isActive, disabled: isDisabled }"——键是类名,值是布尔,值为真才挂上。数组语法是组合清单::class="[baseClass, condition && 'highlight']",数组里还能混对象。两种语法可以叠加在普通 class 上,Vue 会把它们合并。真实项目里常见模式是「静态类写死 + 动态类对象语法控制开关」。

style 绑定同理::style="{ color: textColor, fontSize: size + 'px' }"。属性名用驼峰或带引号的短横线写法都行。一个现代技巧是绑定 CSS 自定义属性::style="{ '--progress': percent }",配合 CSS 里的 var() 可以做高度动态的视觉效果而不必逐属性操作内联样式。

什么时候不用内联 style?颜色、布局这类「主题性」的样式切换,加 class 比在 JS 里拼样式对象更可维护——样式留在 CSS 里,JS 只负责切换状态。内联 style 绑定留给「值本身是数据」的场景:进度条宽度、拖拽元素坐标、用户自定义颜色。

示例

把对象语法/数组语法的合并规则用纯 JS 实现一遍,顺便验证主题切换模式(在本教程构建时被真实执行):

import assert from 'node:assert/strict';

// 模拟 Vue 的 class 归一化:字符串、对象、数组任意嵌套
function normalizeClass(...inputs) {
  const classes = [];
  for (const input of inputs) {
    if (!input) continue;
    if (typeof input === 'string') classes.push(input);
    else if (Array.isArray(input)) classes.push(...normalizeClass(...input).split(' '));
    else if (typeof input === 'object') {
      for (const [name, on] of Object.entries(input)) {
        if (on) classes.push(name);
      }
    }
  }
  return [...new Set(classes)].join(' ');
}

const isActive = true;
const hasError = false;

const tab = normalizeClass('tab', { active: isActive, disabled: false });
assert.strictEqual(tab, 'tab active');

const inputCls = normalizeClass('input', [
  'size-md',
  { 'is-error': hasError, 'is-focused': true },
]);
assert.strictEqual(inputCls, 'input size-md is-focused');

// 主题切换:JS 只切状态,样式留在 CSS
const theme = { value: 'light' };
const pageClass = normalizeClass('page', { dark: theme.value === 'dark' });
assert.strictEqual(pageClass, 'page');
theme.value = 'dark';
const pageClassDark = normalizeClass('page', { dark: theme.value === 'dark' });
assert.strictEqual(pageClassDark, 'page dark');

// CSS 自定义属性做进度条:只传数据,不碰样式
const percent = 0.65;
const style = { '--progress': String(percent) };
assert.strictEqual(style['--progress'], '0.65');

console.log('tab class:', tab);
console.log('input class:', inputCls);
console.log('暗色主题:', pageClassDark, '| 进度变量:', style['--progress']);

组件里的典型用法:

<script setup>
import { ref } from 'vue';

const activeTab = ref('home');
const percent = ref(0.65);
const tabs = ['home', 'list', 'mine'];
</script>

<template>
  <nav>
    <a
      v-for="t in tabs"
      :key="t"
      class="tab"
      :class="{ active: t === activeTab }"
      @click="activeTab = t"
    >
      {{ t }}
    </a>
  </nav>
  <div class="bar" :style="{ width: percent * 100 + '%' }"></div>
</template>

常见坑

  • 对象语法的键忘记加引号:带短横线的类名 'is-error' 必须加引号,否则是语法错误。
  • 在 JS 里大拼样式对象:换主题改间距都写成 :style,样式就失控了;状态类交给 class,数据值交给 style。
  • 数组里直接用条件不加三元/&&[condition ? 'a' : ''] 会留下空字符串类名无害但难看,更推荐对象语法。
  • 忘记单位:style="{ width: w }" 当 w 是数字时 Vue 不自动加 px(个别属性除外),拼字符串 'px' 更稳。
  • 内联样式硬拼优先级:动态样式和 CSS 文件冲突时排查困难,优先用 class 切换而非覆盖。

小结

class 用对象语法管开关、数组语法管组合;「样式归 CSS、状态归 JS」;进度、坐标这类数据型样式用 style 绑定或 CSS 变量。下一章讲交互的入口:事件处理。