资讯动态

Vue基础深入:监视属性、样式绑定与条件渲染全解析

发布时间:2026/10/20 7:50:49 来源:尧图企业网站定制
1. 项目概述这个系列我打算带着大家把Vue的基础一点点啃透。第一篇我们聊了模板语法、数据绑定、事件处理这些最核心的入门内容那篇发出去之后不少朋友留言说“终于搞明白v-model到底干了啥”也有朋友催更让我赶紧把剩下的基础知识点整理出来。这次要聊的三个东西——监视属性、绑定样式、条件渲染——同样是Vue开发里的高频操作。可以这么说只要你写Vue组件几乎每天都要跟它们打交道。监视属性解决的是“数据变了之后我想做点事”的需求绑定样式解决的是“让页面根据数据动态变化外观”的需求条件渲染解决的是“哪些东西该显示、哪些不该显示”的需求。这三个需求覆盖了日常开发中相当大比例的场景把它们弄明白你写起Vue来会顺手很多。这篇文章适合刚学完Vue基础语法、准备上手写实际页面的朋友也适合已经写了段时间Vue、但对某些细节还是一知半解的开发者。我会把每个知识点从原理到实操都讲透包括我在实际项目中踩过的坑和总结出来的经验照着敲一遍基本就能掌握。2. 监视属性watch深度解析2.1 监视属性到底在解决什么问题先想一个场景页面上有个搜索框用户输入关键词之后你想根据关键词的变化去请求接口、筛选列表或者做点别的什么操作。这时候你就需要知道“数据什么时候变了”。Vue的数据绑定能让你把数据渲染到页面上但渲染之外的动作——比如发请求、写日志、联动更新其他数据——就需要一种机制来感知变化。这就是监视属性watch存在的意义。它会盯着你指定的数据源一旦数据发生变化就执行你预先定义好的回调函数。可以把它理解成一个哨兵你告诉它“帮我盯着某个值它一变就通知我”然后它就乖乖在那守着。相比之下计算属性computed的核心是“根据现有数据推导出新数据”它强调的是计算和缓存。而监视属性强调的是“响应变化、执行副作用”。这两者的区别我在后面会专门展开这里先记住一个判断标准如果你要的是“根据数据算出新值并渲染”优先考虑computed如果你要的是“数据变了之后去做某件事”那就用watch。2.2 watch的基本写法与handler配置监视属性的基本写法很直观。先定义数据然后用watch盯着它export default { data() { return { keyword: }; }, watch: { keyword(newValue, oldValue) { console.log(关键词从, oldValue, 变成了, newValue); // 在这里发起搜索请求 } } };这里的keyword对应data中定义的属性名函数接收两个参数第一个是新值第二个是旧值。当keyword变化时这个函数就会被触发。还有一种写法是使用handler配合一些额外的配置项watch: { keyword: { handler(newValue, oldValue) { console.log(关键词变化, oldValue, -, newValue); }, immediate: true } }immediate: true的作用是让watch在初始化时就立即执行一次handler。这个配置在什么场景下有用呢比如你进入页面时就要根据某个已有数据去做初始化操作但又想复用同一个处理函数。没有immediate你就得在created里手动调用一次有了它代码就干净很多。我个人的习惯是如果只需要简单监听直接写函数形式如果涉及immediate或deep配置就改成对象形式。保持两种写法都熟练看别人的代码也不至于懵。2.3 深度监听deep的用法与陷阱默认情况下watch对对象类型的监听是“浅监听”。什么意思看这个例子data() { return { userInfo: { name: 张三, age: 25 } }; }, watch: { userInfo(newValue, oldValue) { console.log(userInfo变了); } }如果你直接修改userInfo.name比如this.userInfo.name 李四这个watch不会触发因为Vue的watch默认只监听引用本身的变化而this.userInfo.name 李四并没有改变userInfo这个对象的引用只是修改了它的内部属性。要让这种对内部属性的修改也能被监听到需要开启深度监听watch: { userInfo: { handler(newValue, oldValue) { console.log(userInfo内部变化了); }, deep: true } }加了deep: true之后Vue会递归遍历userInfo的所有属性给它们都建立监听内部任何一层的数据变化都能触发handler。但这里有个坑要注意deep: true是有性能代价的。如果监听的对象层级很深、数据量很大每次变化都要递归遍历一遍开销会明显上升。我在实际项目中就遇到过监听一个大型表单对象开启deep之后页面明显变卡。所以不要无脑加deep能用浅监听解决的问题就不用深度监听。如果只需要监听对象中的某个具体属性更好的做法是用字符串路径watch: { userInfo.name(newValue, oldValue) { console.log(姓名变了, oldValue, -, newValue); } }这种写法的好处是既精准又不会产生深度遍历的开销而且代码语义非常清晰——一眼就能看出来你盯的是哪个字段。2.4 watch与computed的选型对比这个话题几乎每次讲watch都会被问到。我直接说结论然后解释为什么。适用computed的场景数据B由数据A推导而来而且你需要在模板中渲染B。比如购物车的总价computed: { totalPrice() { return this.cartItems.reduce((sum, item) sum item.price * item.count, 0); } }totalPrice有缓存机制只有cartItems变化时才会重新计算。在模板中用它的次数再多也只会计算一次性能很好。适用watch的场景数据变化之后需要执行异步或开销较大的操作。比如关键词变化后要防抖搜索watch: { keyword() { clearTimeout(this.timer); this.timer setTimeout(() { this.search(this.keyword); }, 300); } }computed里是不能写这种副作用逻辑的它会破坏纯函数的特性而且computed的返回值会被缓存不适合做这种“触发动作”的事情。简单粗暴的判断方法想在模板里展示结果就用computed想在数据变化时执行操作就用watch。两者确实有重叠地带但遵循这个原则写出来的代码逻辑更清晰也好维护。3. 绑定样式Class与Style绑定实用技巧3.1 为什么需要动态绑定样式静态的样式写死在style属性里就行但实际开发中样式往往是跟着数据走的。比如一个标签页当前选中的那个要高亮显示一个列表项满足某个条件的要标红一个按钮加载状态下要显示禁用效果。这些都需要根据数据动态改变元素的样式。Vue提供了两种方案绑定class和绑定style。这两种方案各有适用场景我建议优先使用class绑定把真正的CSS写在样式表里代码会更干净。只有一些需要直接计算样式的场景比如动态设置宽度、颜色值才考虑用style绑定。3.2 class绑定的三种写法Vue的class绑定支持三种写法字符串、数组、对象。逐个来看。字符串写法适用于类名本身就存在的情况简单直接div :classactiveClass内容/divdata() { return { activeClass: active }; }这种写法适合类名以字符串形式存储在数据中的场景不算很常用但偶尔会用到比如从接口拿到一个表示状态的字符串直接映射成类名。数组写法适用于需要同时应用多个类名的场景div :class[baseClass, activeClass, isError ? error-class : ]内容/divdata() { return { baseClass: base-style, activeClass: active-style }; }数组里还可以写三元表达式根据条件动态决定是否添加某个类名。这种写法灵活但表达式写多了看起来会乱。我更推荐的写法是下面这种对象写法。对象写法是实际开发中最常用的它的语义最清晰div :class{ active: isActive, error-text: hasError }内容/divdata() { return { isActive: true, hasError: false }; }对象里每个键是一个类名值是一个布尔表达式。值为真时添加这个类值为假时就不加。这种写法把“什么条件下显示什么样式”的逻辑直接写在了模板里可读性相当好。还有一个实用的小技巧如果你觉得类名的逻辑比较复杂可以在computed里预先计算好让模板保持简洁div :classstatusClass内容/divcomputed: { statusClass() { return { status-success: this.status success, status-warning: this.status warning, status-error: this.status error }; } }尤其是状态类型比较多的时候这种写法比在模板里堆一大串三元表达式要清爽得多。3.3 style绑定的关键细节style绑定通常也写成对象形式div :style{ color: textColor, fontSize: fontSize px, width: progress % }内容/div需要注意几个细节CSS属性名要使用驼峰命名比如fontSize而不是font-size属性值如果是数字Vue会自动添加px后缀但如果是变量拼接出来的字符串就得自己带上单位。还有一点特别重要style绑定中的属性优先级。当同一个CSS属性既出现在class对应的样式表中又出现在style内联样式中时内联样式的优先级更高。这一点在某些场景下能派上用场。比如有一个常规的按钮样式但特定状态下需要覆盖背景色就可以用style绑定来覆盖。不过我要提醒一句能用class解决的尽量用class别什么都往style里塞。内联样式的维护性比较差也容易产生样式覆盖的混乱。3.4 实际项目中的样式绑定组合用法一个常见的场景是做标签页切换。先用数据记录当前选中的标签再根据选中状态动态加类名div classtabs div v-fortab in tabs :keytab.name classtab-item :class{ tab-item-active: currentTab tab.name } clickcurrentTab tab.name {{ tab.label }} /div /divdata() { return { currentTab: all, tabs: [ { name: all, label: 全部 }, { name: pending, label: 待处理 }, { name: done, label: 已完成 } ] }; }v-for循环渲染标签项:class就负责根据currentTab的值决定哪一项高亮。写CSS的时候只需要定义.tab-item的基础样式和.tab-item-active的高亮样式不用在模板里写一堆判断干净利落。进度条也是典型的样式绑定场景div classprogress-outer div classprogress-inner :style{ width: progress % }/div /divcomputed: { progress() { return Math.min(100, Math.round(this.doneCount / this.totalCount * 100)); } }这里用计算属性算出百分比数字然后用style绑定动态设置宽度。如果你在实现过程中觉得自己的做法跟这个不一样不用慌条条大路通罗马保持逻辑清晰就行。4. 条件渲染v-if / v-show / v-else完全指南4.1 v-if与v-show的本质差异Vue提供两套条件渲染方案v-if和v-show很多新手搞不清该用哪个。先看它俩的本质区别。v-if是“真正的条件渲染”。当条件为假时Vue根本不会渲染这个元素到DOM中——DOM里压根没有这个节点。当条件从假变为真时Vue才创建这个元素并插入到DOM中。v-show是“不管条件真假都渲染”。元素始终在DOM中存在只是通过display: none来隐藏或显示。用生活化的例子理解v-if像是你家里直接没有某件家具需要的时候才买回来摆着v-show像是那件家具一直在客厅里只是平时用一块布盖住需要用时把布揭开。这个本质差异带来了三个层面的影响加载性能v-if初始渲染时条件为假的话什么都不用创建性能更好。v-show不管真假都要渲染到DOM初始开销更大。切换性能v-if每次切换都有创建和销毁DOM的过程频繁切换会很耗性能。v-show只是在切换CSS的display属性几乎没有开销。初始可见性v-if还有一个好处如果初始条件为假它连DOM都不会渲染一些敏感内容的初始化就不会执行。当然这个特性用得不多。基于这些差异我的使用经验是切换频率低、初始条件基本不变化的内容用v-if需要频繁切换显示状态的内容用v-show。4.2 v-else与v-else-if的链式用法v-if通常配合v-else-if和v-else使用形成一个完整的条件分支结构div v-ifstatus success操作成功/div div v-else-ifstatus loading加载中.../div div v-else-ifstatus error操作失败/div div v-else未知状态/div几个关键点必须记住第一v-else-if和v-else必须紧跟在v-if或另一个v-else-if的后面中间不能插入其他元素或注释。如果中间隔了别的元素Vue会警告并且无法正确匹配。第二这个链式结构是按顺序判断的命中一个分支后就不再往下走所以要注意条件的先后逻辑。比如把“范围广的条件”放在前面还是后面要根据实际的业务含义来安排。通常应该把具体的条件放在前面兜底的条件放在最后。第三v-else不需要写条件表达式它表示“前面所有条件都不满足时”.从代码可读性的角度这种链式写法比连续多个独立的v-if更清晰与其写div v-ifstatus success成功/div div v-ifstatus loading加载中/div div v-ifstatus error失败/div不如写div v-ifstatus success成功/div div v-else-ifstatus loading加载中/div div v-else-ifstatus error失败/div div v-else未知/div两者的渲染结果可能看起来差不多但前者的逻辑是四个独立判断后者的逻辑是互斥的分支结构后者在处理状态时不容易出现多个分支同时命中的问题。4.3 template标签包裹多个元素一个容易被忽略但非常实用的技巧v-if可以直接用在template标签上对一组元素进行条件渲染而不会产生额外的DOM包裹。看这个场景你需要根据某条件显示两个元素template v-ifisLoggedIn p欢迎回来{{ username }}/p button clicklogout退出登录/button /template template v-else p请先登录/p button clickshowLoginDialog去登录/button /template如果用div包裹的话DOM里会多出一层无意义的div。用template的话Vue只会渲染出里面的两个元素不会产生额外的DOM结构页面的层级更干净。这个技巧在v-for和v-if配合时也很有用我们马上会聊到。4.4 v-for与v-if的优先级问题这一块是高频出错的点必须专门提醒。在早期的Vue 2版本中v-for的优先级高于v-if也就是说当两个指令同时出现在同一个元素上时Vue会先执行循环再执行条件判断。看这个例子ul li v-foritem in items v-ifitem.isVisible{{ item.name }}/li /ul听起来需求很合理只显示isVisible为true的项。但实际执行过程是先循环遍历所有items每遍历一个就检查一次item.isVisible只有为true的才渲染。这个逻辑放在循环内部看似没问题但要注意如果数据量很大的话它仍然会遍历完整的items——即使其中绝大多数项都不需要显示。更好的做法是先用计算属性过滤再循环渲染computed: { visibleItems() { return this.items.filter(item item.isVisible); } }ul li v-foritem in visibleItems :keyitem.id{{ item.name }}/li /ul这样既明确了逻辑又避免了不必要的循环开销。另外要提醒的是在Vue 3中v-if的优先级已经高于v-for所以同样的一段代码在两个版本中执行顺序可能完全不一样。如果你的项目是Vue 2在做代码迁移到Vue 3之前一定要把这种写法先改掉否则很容易出现移过去之后行为不一样的问题。4.5 条件渲染中的key到底多重要配合v-if使用key这个细节很多教材里不强调但实际中经常踩坑。Vue在渲染列表和条件分支时有自己的一套“最小更新”策略。它为了提高性能会尽量复用已有的DOM元素而不是销毁重建。这本身是个好设计但在某些场景下会引发问题。典型的场景一个登录表单在“手机号登录”和“邮箱登录”两种模式之间切换。div v-ifmode phone label手机号/label input typetext placeholder请输入手机号 / /div div v-else label邮箱/label input typetext placeholder请输入邮箱 / /div这里有个问题当你在手机号模式下往输入框里输入了几个字符然后切换到邮箱模式再切回来你会发现手机号输入框里的内容还在。这是因为Vue复用了同一个输入框DOM元素没有重新创建之前输入的内容自然就被保留下来了。解决办法就是给这两个输入框加上不同的key告诉Vue“这是两个不同的元素不要复用”div v-ifmode phone label手机号/label input typetext keyphone-input placeholder请输入手机号 / /div div v-else label邮箱/label input typetext keyemail-input placeholder请输入邮箱 / /div有了不同的keyVue就会在条件切换时重新创建输入框之前的内容就不会保留了。这种问题表现得很隐蔽不仔细测根本发现不了但一旦出现就很困扰。5. 常见问题与排查技巧实录5.1 监听属性不触发的排查清单在项目开发中最容易出问题的往往是watch。我把常见的原因整理成一个排查步骤表,按照这个顺序排查基本能快速找到问题。排查步骤检查内容解决方案1数据是否真的变了在handler里加log或断点确认数据的变化是否到达了watch2监听的是不是对象内部属性对象内部属性变化需要加deep或用字符串路径精确监听3数据变化是否发生在同一个tick有的修改被合并了用$nextTick或在变化后确认值4是否在computed或watch中修改了监听的数据形成死循环或意外连锁需要重新设计数据流5是否在初始化之后才赋值的数据初始赋值用immediate: true来捕获这里单独说下第4点。watch的handler里修改被监听的数据本身会导致循环触发。比如watch: { count(newVal) { this.count newVal 1; // 这会导致无限循环 } }count一变handler执行又把count改了又会触发handler……形成一个死循环。实际的业务中这种情况通常是逻辑设计出了问题。解决办法是重新梳理数据流到底哪份数据是“源头”哪份是“结果”不要让一份数据自己依赖自己。5.2 样式绑定不生效的几种原因样式绑定看着简单但偶尔也会碰到“明明条件满足了样式却没生效”的情况。常见原因有三个。第一个是类名冲突。某个类名在多个地方被赋了不同的样式后加载的样式表覆盖了先加载的。这种问题通常不是Vue层面的问题而是CSS层叠优先级的问题。排查思路是在浏览器开发者工具里看看这个元素上到底应用了哪些样式规则优先级如何。第二个是style绑定的属性名写错了。CSS属性在Vue的style绑定里要用驼峰命名比如backgroundColor而不是background-color。写错了Vue不会报错但样式就不生效很难发现。第三个是类型问题。比如width属性绑定的值是数字会自动加px但如果你绑定的是一个字符串50%就直接作为CSS值使用。有时候数据从接口来可能是字符串也可能数字切换类型时表现会不一样。可以在computed里先处理好数据的类型和格式再绑定到样式上。5.3 条件渲染常见决策误区很多新手在v-if和v-show之间纠结我再说说常见的决策误区。误区一处处用v-if觉得“不渲染就没开销”。其实如果切换频繁创建和销毁DOM的开销反而比一直保留一个隐藏节点要大。比如一个展开/收起的折叠面板用户可能反复点击用v-show更合适。误区二到处用v-show觉得“反正都在DOM里切换快”。但如果初始状态只有一个分支会用到而且列表很长那始终渲染着也是浪费。比如一个很少用到的弹窗用v-if让它在需要时才创建更省资源。误区三在同一个元素上混用v-for和v-if这在Vue 2里容易产生性能怪相。正确做法是先过滤数据再循环渲染这个上面已经提过。我的经验法则在“显示/隐藏”频率高、状态切换快的场景中用v-show在“要不要存在”这些低频切换的场景中用v-if。拿布局来说导航栏的折叠展开用v-show权限控制的模块显隐用v-if。5.4 性能优化经验与实测心得最后分享一点针对这三个特性的性能优化经验都是我实际测试和项目里总结出来的。关于watch的性能最核心的一条是监听粒度越小越好。尽量精确到具体属性比如用userInfo.name而不是对整个对象加deep: true。如果确实要监听整个对象考虑用JSON.stringify对比新旧值来减少不必要的操作。不过这个方法也是双刃剑字符串化大对象本身也有开销需谨慎使用。关于样式绑定的性能核心是减少不必要的类名计算。如果类名的逻辑比较复杂用computed缓存计算结果而不是在模板中内联写大量的三元表达式。模板中的表达式每次渲染都会重新计算而computed有缓存数据没变就直接用缓存结果。关于条件渲染的性能列表特别大的时候避免用v-if频繁切换然后重新渲染整个列表。更好的方案是分页、懒加载或者用虚拟滚动方案。我在一个管理后台的项目里做过一次优化一个表格页开始时每条记录都同时判断权限、状态、可见性等多个条件导致列表渲染特别慢。后来我把这些判断收敛成computed只对外暴露一个visibleItems渲染速度明显提升。这类优化思路一定要记住模板里少写复杂逻辑数据层整理清楚了页面性能自然就好。6. 总结与扩展建议到这里Vue基础的监视属性、绑定样式、条件渲染这三个主题就都过完了。我再从个人经验角度补几句。这三个知识点单独看都不难难的是在真实项目里组合使用。我见过不少同事把这些特性用得很散watch里塞一堆逻辑、class绑定写了一大串三元表达式、v-if跟v-for混在一起还出了bug。这些问题的根源不是不懂语法而是没有形成一套清晰的使用习惯。我的习惯总结下来就三条watch只做“监听变化做动作”的事复杂的派生状态交给computedclass绑定优先用对象写法复杂的判断收敛到computed条件渲染先问一句“切换频率高不高”再决定用v-if还是v-show。养成这三个习惯写出来的代码会清晰很多也少踩很多坑。接下来这个系列还可以继续深入组件通信、插槽、生命周期、Vuex状态管理等话题。如果这篇文章对你有帮助或者你在实战中遇到了其他问题欢迎在评论区留言交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑