前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Vue之class与style绑定 对象与数组两种语法(三)

首页2018-08-26 14:02:32Front-End
Vueclass绑定style绑定

上一篇把八类绑定语法过了一遍,其中 class 和 style 是最容易写歪的两个。原因也简单,它们支持的写法太多了,对象、数组、数组里套对象、计算属性返回对象,四种写法能解决同一个问题,新手根本不知道该挑哪个。我自己刚写 Vue 那会儿是全用三元字符串拼的,一个标签上拼出三层嵌套三元,过两周回头看谁都不认识。这篇把 class 和 style 的全部写法排一遍,重点说清每种写法适合什么场景,以及那个官方称为「常用且强大」的计算属性模式到底强在哪。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 为什么 Vue 要给 class 和 style 开小灶,而不是当普通属性处理
  • class 对象语法的四种形态,从写死的键值到计算属性返回对象
  • class 数组语法,以及数组里嵌对象这个组合写法
  • 在自定义组件上写 class 会发生什么,类名落到哪个元素上
  • 内联 style 的对象语法、对象数组语法、自动前缀和多重值兜底
  • 这些写法在 Vue 3 里有哪些变化

# 一、为什么 class 和 style 要特殊对待

v-bind 处理普通属性时逻辑很直白,表达式算出什么值就往属性上放什么值。但 class 和 style 不行。

你想想看,一个元素上的类名往往来自好几处。有布局用的固定类,有状态相关的 active、disabled,有主题相关的 dark。如果 :class 只接受字符串,那你每次都得自己拼,还得处理空格、处理某个类要不要加的判断,最后拼出一坨没法读的表达式。

所以 Vue 在 v-bind 里给这两个属性做了增强,允许传对象和数组,框架负责把它们展开成最终的字符串,还会自动和标签上写死的静态 class 合并。这一层增强省掉的正是字符串拼接那部分脏活。

# 二、绑定 class 的对象语法

对象语法的规则一句话,键是类名,值是布尔,值为真类名就上。数据属性一变,class 列表跟着更新。

# 2.1 最简形态

<div id="app">
    <div v-bind:class="{ active: isActive }"></div>
</div>
<script>
var app = new Vue({
    el: "#app",
    data: {
        msg: "对象语法",
        isActive: true
    }
});
</script>
@前端进阶之旅: 代码已经复制到剪贴板

isActive 为 true 时渲染成 class="active",改成 false 类名就没了。注意键名如果带连字符,比如 text-danger,得写成 'text-danger': hasError,加引号,否则会被 JS 当成减法表达式。

# 2.2 和静态 class 共存

v-bind:class 不会覆盖标签上原有的 class,两者是合并关系:

<style>
.active {
    width: 100px;
    height: 100px;
    background: red;
}
</style>

<div id="app">
    <div class="box" v-bind:class="{ active: isActive }"></div>
</div>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            msg: "对象语法",
            isActive: true
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

最终渲染出来是 class="box active"。这个特性用得很多,静态类负责基础样式,动态类负责状态叠加,职责分得很清楚。原文这段的 CSS 直接裸写在 HTML 里没有包 <style> 标签,上面补上了。

# 2.3 直接绑定 data 里的一个对象

模板里的对象字面量一长就影响可读性,可以把整个对象搬到 data 里:

<style>
.active1 {
    width: 100px;
    height: 100px;
    margin-top: 10px;
    border: 1px solid #ccc;
}
</style>

<div id="app">
    <div v-bind:class="classObj"></div>
</div>
<script>
var app = new Vue({
    el: "#app",
    data: {
        classObj: {
            active: true,
            aaa: false
        }
    }
});
</script>
@前端进阶之旅: 代码已经复制到剪贴板

模板一下子干净了。代价是这个对象里的每个键都得提前在 data 里声明好,后加的键在 Vue 2 里不是响应式的,得走 this.$set。

# 2.4 用计算属性返回对象

这是官方文档专门点名的一种模式,也是我实际项目里用得最多的:

<style>
.aaa {
    background: green;
    width: 100px;
    height: 100px;
    margin-top: 10px;
}
</style>

<div id="app">
    <div v-bind:class="classObject"></div>
</div>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            msg: "对象语法",
            isActive: true
        },
        computed: {
            classObject: function () {
                return {
                    aaa: this.isActive
                }
            }
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

强在哪?强在它把「什么条件下加什么类」这段逻辑从模板里搬到了 JS 里。真实业务的判断条件从来不是一个布尔,往往是「状态是 pending 且当前用户有权限且不在只读模式」。这种条件塞进模板就是灾难,放进计算属性里可以写 if else、可以写注释、可以单独测。

而且计算属性有缓存,依赖不变就不会重新计算,比在模板里写复杂表达式效率高。计算属性和侦听器的细节在 vue计算属性与数据监听(十) 里。

原文这里把计算属性命名成了 Obj,首字母大写在 JS 里通常表示构造函数,容易误导,上面改成了 classObject,功能一样。

# 三、绑定 class 的数组语法

数组语法传的是一组类名,Vue 把它们拼起来。

# 3.1 基本形态

<style>
    .active {
        width: 100px;
        height: 100px;
        background: red;
    }
    .active1 {
        color: yellow;
    }
    .aaa {
       border: 5px solid #ccc;
    }
</style>

<div id="app">
    <div class="box" v-bind:class="[isActive, isActive1, isActive2]">{{ msg }}</div>
</div>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            msg: "数组语法",
            isActive: "active",
            isActive1: "active1",
            isActive2: "aaa"
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

这里有个容易懵的点,数组里写的是变量名,变量的值才是类名字符串。isActive 这个名字起得有误导性,它看着像布尔,实际存的是 "active" 这个字符串。要直接写死类名,得加引号,写成 ['active', 'aaa']。

# 3.2 用三元表达式做条件

数组里的每一项都可以是表达式,所以条件类名可以这么写:

<div id="app">
    <div class="box" v-bind:class="[isActive, isActive1, isActive5 ? isActive2 : '']">{{ msg }}</div>
</div>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            msg: "数组语法",
            isActive5: false,
            isActive: "active",
            isActive1: "active1",
            isActive2: "aaa"
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

条件为假时返回空字符串,Vue 会自动过滤掉,不会渲染出多余的空格类名。

# 3.3 数组里嵌对象

三元只有一两个还行,条件一多整行就废了。这时候把对象语法塞进数组里:

<div id="app">
    <div class="box" v-bind:class="[isActive, { active1: isActive5 }, isActive5 ? isActive2 : '']">{{ msg }}</div>
</div>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            msg: "数组语法",
            isActive5: true,
            isActive: "active",
            isActive1: "active1",
            isActive2: "aaa"
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

数组负责「一定要加的类」,里面的对象负责「按条件加的类」,读起来比三元清楚。不过说实话,一旦写到这个复杂度,我更倾向于直接换成 2.4 那个计算属性方案。

# 四、在组件上使用 class

在自定义组件的标签上写 class,这些类会被加到组件的根元素上,根元素上已有的类不会被覆盖,两边是合并的:

<style>
    .active1 {
        width: 100px;
        background: red;
    }
    .aaa {
        border: 5px solid #ccc;
    }
    .bbb {
        height: 100px;
    }
</style>

<div id="app">
    <tanchu v-bind:class="classObj"></tanchu>
</div>
<script>
    Vue.component('tanchu', {
        template: `<div class="bbb">
                <input type="button" value="弹出"/>
            </div>`
    })

    var app = new Vue({
        el: "#app",
        data: {
            classObj: {
                active1: true,
                aaa: true
            }
        }
    })
</script>
@前端进阶之旅: 代码已经复制到剪贴板

最终根元素上是 class="bbb active1 aaa"。这个行为让封装组件时很省心,使用方想给你的组件加个外边距,直接在标签上写类就行,不需要你在组件内部专门开一个 prop。

这里有个坑要注意。这套自动继承只在组件模板有单个根元素时才成立。如果模板有多个并列的根节点,Vue 不知道该往哪个上放,类名就会丢失。Vue 2 本来就不允许多根节点,所以碰不到;Vue 3 允许了,这个问题就浮出来了,后面第六节会说。

# 五、绑定内联样式

# 5.1 对象语法

fe
  • 一、为什么 class 和 style 要特殊对待
  • 二、绑定 class 的对象语法
    • 2.1 最简形态
    • 2.2 和静态 class 共存
    • 2.3 直接绑定 data 里的一个对象
    • 2.4 用计算属性返回对象
  • 三、绑定 class 的数组语法
    • 3.1 基本形态
    • 3.2 用三元表达式做条件
    • 3.3 数组里嵌对象
  • 四、在组件上使用 class
  • 五、绑定内联样式
    • 5.1 对象语法
    • 5.2 数组语法
    • 5.3 自动添加前缀和多重值
  • 六、Vue 3 里的差异
  • 总结
  • 参考

← Vue 基本指令用法详解与常见坑(四)Vue中的数据绑定 八类绑定语法一次讲透(二) →