一、问题复现:当给对象添加新属性后界面纹丝不动
1.1 现象描述
在日常 Vue 开发中,我们经常会遇到这样的困惑:明明数据已经成功添加到对象上,控制台打印也能看到新属性,但界面就是毫无反应。这就是本文要探讨的核心问题——为什么 Vue 中给对象添加新属性后界面不刷新?
1.2 最小复现示例
下面这段代码可以完美复现该问题:
data() {
return {
user: {
name: '张三',
age: 25
}
}
},
methods: {
addProperty() {
this.user.gender = '男'
console.log(this.user)
}
}
点击按钮后,控制台输出 {name: '张三', age: 25, gender: '男'},但模板中渲染的 {{ user.gender }} 却始终为空。
1.3 问题定位流程
#publish-mermaid-1785343297593-0{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297593-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297593-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297593-0 .error-icon{fill:#552222;}#publish-mermaid-1785343297593-0 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297593-0 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297593-0 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297593-0 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297593-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297593-0 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297593-0 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297593-0 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297593-0 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297593-0 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297593-0 p{margin:0;}#publish-mermaid-1785343297593-0 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297593-0 .cluster-label text{fill:#333;}#publish-mermaid-1785343297593-0 .cluster-label span{color:#333;}#publish-mermaid-1785343297593-0 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297593-0 .label text,#publish-mermaid-1785343297593-0 span{fill:#333;color:#333;}#publish-mermaid-1785343297593-0 .node rect,#publish-mermaid-1785343297593-0 .node circle,#publish-mermaid-1785343297593-0 .node ellipse,#publish-mermaid-1785343297593-0 .node polygon,#publish-mermaid-1785343297593-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297593-0 .rough-node .label text,#publish-mermaid-1785343297593-0 .node .label text,#publish-mermaid-1785343297593-0 .image-shape .label,#publish-mermaid-1785343297593-0 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297593-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297593-0 .rough-node .label,#publish-mermaid-1785343297593-0 .node .label,#publish-mermaid-1785343297593-0 .image-shape .label,#publish-mermaid-1785343297593-0 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297593-0 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297593-0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297593-0 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297593-0 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297593-0 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297593-0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297593-0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297593-0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297593-0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297593-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297593-0 .cluster text{fill:#333;}#publish-mermaid-1785343297593-0 .cluster span{color:#333;}#publish-mermaid-1785343297593-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297593-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297593-0 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297593-0 .icon-shape,#publish-mermaid-1785343297593-0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297593-0 .icon-shape p,#publish-mermaid-1785343297593-0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297593-0 .icon-shape .label rect,#publish-mermaid-1785343297593-0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297593-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297593-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297593-0 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297593-0 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297593-0 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297593-0 :root{–mermaid-font-family:inherit;}
否
是
否
是
给对象添加新属性
是否触发响应式更新
界面不刷新
界面正常更新
排查属性添加方式
是否使用 Vue.set
Object.defineProperty 未劫持新属性
检查属性是否已存在
改用 Vue.set 或 $set
使用其他方案
二、原理剖析:Vue 2 响应式机制的边界与限制
2.1 Object.defineProperty 的工作机制
Vue 2 的响应式系统基于 Object.defineProperty 实现。当我们在 data 中声明一个对象时,Vue 会通过 Object.defineProperty 将对象的所有现有属性转换为 getter/setter,整个过程如下:
#publish-mermaid-1785343297665-1{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297665-1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297665-1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297665-1 .error-icon{fill:#552222;}#publish-mermaid-1785343297665-1 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297665-1 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297665-1 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297665-1 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297665-1 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297665-1 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297665-1 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297665-1 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297665-1 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297665-1 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297665-1 p{margin:0;}#publish-mermaid-1785343297665-1 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297665-1 .cluster-label text{fill:#333;}#publish-mermaid-1785343297665-1 .cluster-label span{color:#333;}#publish-mermaid-1785343297665-1 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297665-1 .label text,#publish-mermaid-1785343297665-1 span{fill:#333;color:#333;}#publish-mermaid-1785343297665-1 .node rect,#publish-mermaid-1785343297665-1 .node circle,#publish-mermaid-1785343297665-1 .node ellipse,#publish-mermaid-1785343297665-1 .node polygon,#publish-mermaid-1785343297665-1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297665-1 .rough-node .label text,#publish-mermaid-1785343297665-1 .node .label text,#publish-mermaid-1785343297665-1 .image-shape .label,#publish-mermaid-1785343297665-1 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297665-1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297665-1 .rough-node .label,#publish-mermaid-1785343297665-1 .node .label,#publish-mermaid-1785343297665-1 .image-shape .label,#publish-mermaid-1785343297665-1 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297665-1 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297665-1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297665-1 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297665-1 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297665-1 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297665-1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297665-1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297665-1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297665-1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297665-1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297665-1 .cluster text{fill:#333;}#publish-mermaid-1785343297665-1 .cluster span{color:#333;}#publish-mermaid-1785343297665-1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297665-1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297665-1 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297665-1 .icon-shape,#publish-mermaid-1785343297665-1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297665-1 .icon-shape p,#publish-mermaid-1785343297665-1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297665-1 .icon-shape .label rect,#publish-mermaid-1785343297665-1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297665-1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297665-1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297665-1 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297665-1 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297665-1 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297665-1 :root{–mermaid-font-family:inherit;}
initData
observe 数据
遍历对象已有属性
Object.defineProperty
定义 getter
定义 setter
依赖收集
派发更新
2.2 为什么新属性无法被劫持
Object.defineProperty 的核心局限在于:它只能劫持已经存在的属性,无法检测到对象属性的新增或删除。这就解释了为什么 this.user.gender = '男' 后界面不刷新——因为 gender 这个属性在 Vue 初始化时根本不存在,所以没有被转换为响应式属性,自然也就无法触发依赖更新。
2.3 数组的特殊处理方式
数组也有类似问题,直接通过索引修改数组 this.list[0] = newValue 同样不会触发更新。Vue 2 通过重写数组的 7 个变异方法来解决这个问题:
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method => {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(…args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
})
})
2.4 响应式更新完整流程
理解完整的响应式更新流程,有助于我们从全局视角把握问题:
#publish-mermaid-1785343297725-2{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297725-2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297725-2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297725-2 .error-icon{fill:#552222;}#publish-mermaid-1785343297725-2 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297725-2 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297725-2 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297725-2 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297725-2 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297725-2 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297725-2 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297725-2 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297725-2 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297725-2 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297725-2 p{margin:0;}#publish-mermaid-1785343297725-2 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297725-2 .cluster-label text{fill:#333;}#publish-mermaid-1785343297725-2 .cluster-label span{color:#333;}#publish-mermaid-1785343297725-2 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297725-2 .label text,#publish-mermaid-1785343297725-2 span{fill:#333;color:#333;}#publish-mermaid-1785343297725-2 .node rect,#publish-mermaid-1785343297725-2 .node circle,#publish-mermaid-1785343297725-2 .node ellipse,#publish-mermaid-1785343297725-2 .node polygon,#publish-mermaid-1785343297725-2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297725-2 .rough-node .label text,#publish-mermaid-1785343297725-2 .node .label text,#publish-mermaid-1785343297725-2 .image-shape .label,#publish-mermaid-1785343297725-2 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297725-2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297725-2 .rough-node .label,#publish-mermaid-1785343297725-2 .node .label,#publish-mermaid-1785343297725-2 .image-shape .label,#publish-mermaid-1785343297725-2 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297725-2 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297725-2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297725-2 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297725-2 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297725-2 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297725-2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297725-2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297725-2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297725-2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297725-2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297725-2 .cluster text{fill:#333;}#publish-mermaid-1785343297725-2 .cluster span{color:#333;}#publish-mermaid-1785343297725-2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297725-2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297725-2 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297725-2 .icon-shape,#publish-mermaid-1785343297725-2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297725-2 .icon-shape p,#publish-mermaid-1785343297725-2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297725-2 .icon-shape .label rect,#publish-mermaid-1785343297725-2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297725-2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297725-2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297725-2 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297725-2 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297725-2 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297725-2 :root{–mermaid-font-family:inherit;}
组件挂载
render 函数执行
读取响应式数据
触发 getter
依赖收集 Dep.depend
建立 Watcher 与 Dep 关系
修改数据
触发 setter
Dep.notify 通知 Watcher
Watcher 执行 update
进入异步更新队列
nextTick 触发 patch
重新渲染 DOM
三、解决方案:让新属性也能触发视图更新
3.1 使用 Vue.set 或 this.$set
这是最推荐的解决方案。Vue.set(target, key, value) 会为新属性添加响应式:
methods: {
addProperty() {
this.$set(this.user, 'gender', '男')
}
}
其内部实现原理简化如下:
function set(target, key, val) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = target.__ob__
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
3.2 替换整个对象
通过创建一个新对象并赋值,可以触发原有属性的 setter,从而实现响应式更新:
methods: {
addProperty() {
this.user = Object.assign({}, this.user, { gender: '男' })
}
}
或使用展开运算符:
methods: {
addProperty() {
this.user = { …this.user, gender: '男' }
}
}
3.3 使用 this.$forceUpdate 的误区
this.$forceUpdate 可以强制组件重新渲染,但它只是一个临时方案,并不推荐作为常规手段:
methods: {
addProperty() {
this.user.gender = '男'
this.$forceUpdate()
}
}
该方案的缺点如下:
3.4 方案对比与选择
#publish-mermaid-1785343297822-3{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297822-3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297822-3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297822-3 .error-icon{fill:#552222;}#publish-mermaid-1785343297822-3 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297822-3 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297822-3 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297822-3 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297822-3 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297822-3 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297822-3 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297822-3 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297822-3 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297822-3 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297822-3 p{margin:0;}#publish-mermaid-1785343297822-3 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297822-3 .cluster-label text{fill:#333;}#publish-mermaid-1785343297822-3 .cluster-label span{color:#333;}#publish-mermaid-1785343297822-3 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297822-3 .label text,#publish-mermaid-1785343297822-3 span{fill:#333;color:#333;}#publish-mermaid-1785343297822-3 .node rect,#publish-mermaid-1785343297822-3 .node circle,#publish-mermaid-1785343297822-3 .node ellipse,#publish-mermaid-1785343297822-3 .node polygon,#publish-mermaid-1785343297822-3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297822-3 .rough-node .label text,#publish-mermaid-1785343297822-3 .node .label text,#publish-mermaid-1785343297822-3 .image-shape .label,#publish-mermaid-1785343297822-3 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297822-3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297822-3 .rough-node .label,#publish-mermaid-1785343297822-3 .node .label,#publish-mermaid-1785343297822-3 .image-shape .label,#publish-mermaid-1785343297822-3 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297822-3 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297822-3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297822-3 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297822-3 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297822-3 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297822-3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297822-3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297822-3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297822-3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297822-3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297822-3 .cluster text{fill:#333;}#publish-mermaid-1785343297822-3 .cluster span{color:#333;}#publish-mermaid-1785343297822-3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297822-3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297822-3 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297822-3 .icon-shape,#publish-mermaid-1785343297822-3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297822-3 .icon-shape p,#publish-mermaid-1785343297822-3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297822-3 .icon-shape .label rect,#publish-mermaid-1785343297822-3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297822-3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297822-3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297822-3 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297822-3 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297822-3 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297822-3 :root{–mermaid-font-family:inherit;}
单个属性
多个属性
是
否
新增属性方案选择
属性数量
使用 $set
是否需要保留原对象引用
逐个使用 $set
Object.assign 替换对象
推荐方案
四、Vue 3 的进化:Proxy 带来的彻底改变
4.1 Proxy 的优势
Vue 3 使用 Proxy 重写了响应式系统,从根本上解决了新增属性无法响应的问题:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
}
})
}
Proxy 可以拦截 13 种操作,包括 has、ownKeys、deleteProperty 等,因此能检测到属性的新增和删除,无需再用 $set。
4.2 Vue 3 响应式流程
#publish-mermaid-1785343297884-4{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297884-4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297884-4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297884-4 .error-icon{fill:#552222;}#publish-mermaid-1785343297884-4 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297884-4 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297884-4 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297884-4 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297884-4 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297884-4 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297884-4 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297884-4 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297884-4 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297884-4 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297884-4 p{margin:0;}#publish-mermaid-1785343297884-4 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297884-4 .cluster-label text{fill:#333;}#publish-mermaid-1785343297884-4 .cluster-label span{color:#333;}#publish-mermaid-1785343297884-4 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297884-4 .label text,#publish-mermaid-1785343297884-4 span{fill:#333;color:#333;}#publish-mermaid-1785343297884-4 .node rect,#publish-mermaid-1785343297884-4 .node circle,#publish-mermaid-1785343297884-4 .node ellipse,#publish-mermaid-1785343297884-4 .node polygon,#publish-mermaid-1785343297884-4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297884-4 .rough-node .label text,#publish-mermaid-1785343297884-4 .node .label text,#publish-mermaid-1785343297884-4 .image-shape .label,#publish-mermaid-1785343297884-4 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297884-4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297884-4 .rough-node .label,#publish-mermaid-1785343297884-4 .node .label,#publish-mermaid-1785343297884-4 .image-shape .label,#publish-mermaid-1785343297884-4 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297884-4 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297884-4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297884-4 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297884-4 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297884-4 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297884-4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297884-4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297884-4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297884-4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297884-4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297884-4 .cluster text{fill:#333;}#publish-mermaid-1785343297884-4 .cluster span{color:#333;}#publish-mermaid-1785343297884-4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297884-4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297884-4 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297884-4 .icon-shape,#publish-mermaid-1785343297884-4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297884-4 .icon-shape p,#publish-mermaid-1785343297884-4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297884-4 .icon-shape .label rect,#publish-mermaid-1785343297884-4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297884-4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297884-4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297884-4 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297884-4 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297884-4 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297884-4 :root{–mermaid-font-family:inherit;}
reactive 包装对象
创建 Proxy 代理
组件 render 读取属性
触发 get 拦截器
track 收集依赖
建立 effect 与依赖映射
修改或新增属性
触发 set 拦截器
trigger 派发更新
scheduler 调度 effect
组件重新渲染
4.3 Vue 2 与 Vue 3 响应式对比
| 对比维度 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|———|——————————-|—————|
| 新增属性 | 无法检测,需手动 $set | 自动检测 |
| 删除属性 | 无法检测,需 $delete | 自动检测 |
| 数组索引修改 | 需 $set | 自动检测 |
| Map/Set 支持 | 不支持 | 原生支持 |
| 性能 | 初始化时深度遍历 | 惰性响应式 |
五、实战避坑指南:日常开发中的注意事项
5.1 常见踩坑场景
5.2 调试技巧
可以通过检查对象的 __ob__ 属性来判断是否为响应式对象:
console.log(this.user.__ob__)
console.log(this.user.__ob__.value.gender)
如果新属性不在 __ob__.value 中被 observe,则说明它不是响应式的。
5.3 最佳实践总结
5.4 完整避坑决策图
#publish-mermaid-1785343297955-5{font-family:inherit;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1785343297955-5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297955-5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1785343297955-5 .error-icon{fill:#552222;}#publish-mermaid-1785343297955-5 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1785343297955-5 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1785343297955-5 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1785343297955-5 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1785343297955-5 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1785343297955-5 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1785343297955-5 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1785343297955-5 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1785343297955-5 .marker.cross{stroke:#333333;}#publish-mermaid-1785343297955-5 svg{font-family:inherit;font-size:16px;}#publish-mermaid-1785343297955-5 p{margin:0;}#publish-mermaid-1785343297955-5 .label{font-family:inherit;color:#333;}#publish-mermaid-1785343297955-5 .cluster-label text{fill:#333;}#publish-mermaid-1785343297955-5 .cluster-label span{color:#333;}#publish-mermaid-1785343297955-5 .cluster-label span p{background-color:transparent;}#publish-mermaid-1785343297955-5 .label text,#publish-mermaid-1785343297955-5 span{fill:#333;color:#333;}#publish-mermaid-1785343297955-5 .node rect,#publish-mermaid-1785343297955-5 .node circle,#publish-mermaid-1785343297955-5 .node ellipse,#publish-mermaid-1785343297955-5 .node polygon,#publish-mermaid-1785343297955-5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297955-5 .rough-node .label text,#publish-mermaid-1785343297955-5 .node .label text,#publish-mermaid-1785343297955-5 .image-shape .label,#publish-mermaid-1785343297955-5 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1785343297955-5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1785343297955-5 .rough-node .label,#publish-mermaid-1785343297955-5 .node .label,#publish-mermaid-1785343297955-5 .image-shape .label,#publish-mermaid-1785343297955-5 .icon-shape .label{text-align:center;}#publish-mermaid-1785343297955-5 .node.clickable{cursor:pointer;}#publish-mermaid-1785343297955-5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1785343297955-5 .arrowheadPath{fill:#333333;}#publish-mermaid-1785343297955-5 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1785343297955-5 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1785343297955-5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297955-5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297955-5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297955-5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1785343297955-5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1785343297955-5 .cluster text{fill:#333;}#publish-mermaid-1785343297955-5 .cluster span{color:#333;}#publish-mermaid-1785343297955-5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:inherit;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1785343297955-5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1785343297955-5 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1785343297955-5 .icon-shape,#publish-mermaid-1785343297955-5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1785343297955-5 .icon-shape p,#publish-mermaid-1785343297955-5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1785343297955-5 .icon-shape .label rect,#publish-mermaid-1785343297955-5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1785343297955-5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1785343297955-5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1785343297955-5 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node rect,#publish-mermaid-1785343297955-5 [data-look=\”neo\”].cluster rect,#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297955-5 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1785343297955-5 :root{–mermaid-font-family:inherit;}
Vue 2
Vue 3
this.obj.newProp = val
this.$set
Object.assign 替换对象
需要给对象添加新属性
Vue 版本
添加方式
直接赋值即可
界面不刷新
正常响应
排查并改用 $set
问题解决

