VUE 2.0取字典集后无法更新节点的理解
(前端新手上路的学习日记)
我在现在的项目中,字典集合是放在数据库中,通过redis写了一层缓存,需要我们前端调接口获取来渲染页面的,一个一个调字典又会很卡。故而有getDictList方法来通过传一个由字典id构造而成的数组批量获取字典集合。
进而用字典的id值当作key,构造一个dictList对象(用数组的话会很坑,具体的后文可见)的方式来对表单,表格等节点进行取字典集或通过filter函数来获取字典id渲染出字典值。
html取字典部分是这样写的
<el-select
size="medium"
class="search-sort"
v-model="findParam.type"
placeholder="请选择"
>
<el-option
v-for="item in useDict['1059']"
:key="item.dic_id"
:label="item.dic_name"
:value="item.dic_id"
></el-option>
</el-select>
初始代码
代码段:
getDictList(this.dictIdList).then(res=>{
// console.log('字典id集合',this.dictIdList)
if(res.data.code == 20000){
res.data.data.forEach((item,index)=>{
//对字典进行处理 方便后续选择框进行选项绑定
item.sysDictItemList = item.sysDictItemList.map(item => {
return { dic_name: item.dictItemName, dic_id: item.dictItemId, no: item.dictItemId };
});
this.useDict[item.dictId] = item.sysDictItemList // 用赋值数据是非响应式的 赋值到最后一个数据再进行节点更新
})
console.log( this.useDict,'方法一的字典集合')
// this.$forceUpdate() // 更新视图后更新表单 来获取字典
}else{
this.$message({
type: "warning",
message: "查询字典集接口报错"+ res.data.msg,
});
}
})
这样写之后会带来一个问题,就是由于取字典接口的回调可能在页面节点渲染之后,而
this.useDict[item.dictId] = item.sysDictItemList
// …
检测变化的注意事项
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
当你利用索引直接设置一个数组项或对象属性时,例如:vm.items[indexOfItem] = newValue… //
这种方式的赋值语句不会触发页面更新,究其原因自我理解是通过option绑定的方式可能并不是深度监听,一开始渲染时useDict是空的,useDict[‘1059’] 也是空的,因而无法添加getter和setter函数,故而对应前端节点并未订阅这个 useDict[‘1059’] 的数据,哪怕 useDict[‘1059’] 获取到了之后也无法通过双向绑定原理使得前端页面更新视图层,这是刷新节点,重新获取,使得节点的更新在获取到 useDict[‘1059’]之后才行
// 此处用来存放字典集合的useDict的数据类型需要是对象类型,如果是数组的话,给useDict[10000]赋值就会带来9999个空项,会极大程度的增加浏览器的负荷 //
但拖动页面之后可以获取到字典集合(一度让我十分惊讶,可能是拖动页面导致VUE触发了更新视图层的函数?有大佬看到了欢迎指点)
去百度了之后发现了三种解决办法
1.通过Key将字典集合和HTML节点关联起来
对表单页面绑定key值(成功)
<el-form
:key="useDict.length"
:model="formData"
>
通过useDict的长度当作我方表单的键值,在获取了字典集合之后,useDict的长度发生变化,键值改变,会强行更新整个表格,后来思考了一下,其实只需要更新一个表单中的相应的取了字典的节点就可以了,于是换了一种写法
尝试对对应节点绑定Key值(失败)
<el-select
size="medium"
class="search-sort"
v-model="findParam.type"
placeholder="请选择"
>
<el-option
v-for="item in useDict['1059']"
:key="useDict['1059']" //这样写是期望能在字典值获取后监听到变化从而通过key的变化更新节点
:label="item.dic_name"
:value="item.dic_id"
></el-option>
</el-select>
得到VUE报错:
Avoid using non-primitive value as key, use string/number value instead
自我理解:在节点渲染中需要保证每个节点的键值是不同的,由于确实在一个表单中存在着几个取相同字典的节点存在,这样写可能确实带来隐患,所以希望将对应字典和Html节点通过订阅者原则绑定起来的方式暂时只能走到这里(我太菜了)
2.用VUE$.Set 语句将响应式数据添加到嵌套的对象上
// 用Vue.set(成功)
this.$set(this.useDict,item.dictId,item.sysDictItemList)
// 用Array.prototype.splice (没试过 应该和.set 效果差不多)
this.useDict.splice(item.dictId, 1, item.sysDictItemList)
则有
getDictList(this.dictIdList).then(res=>{
// console.log('字典id集合',this.dictIdList)
if(res.data.code == 20000){
res.data.data.forEach((item,index)=>{
//对字典进行处理 方便后续选择框进行选项绑定
item.sysDictItemList = item.sysDictItemList.map(item => {
return { dic_name: item.dictItemName, dic_id: item.dictItemId, no: item.dictItemId };
});
this.$set(this.useDict,item.dictId,item.sysDictItemList) // 用$set 加数据是响应式的 会带动更新节点
})
console.log( this.useDict,'方法一的字典集合')
// this.$forceUpdate() // 更新视图后更新表单 来获取字典
}else{
this.$message({
type: "warning",
message: "查询字典集接口报错"+ res.data.msg,
});
}
})
但是通过自己的理解(不一定对),在循环时触发视图更新可能? 会造成部分的资源损耗,
后续通过学习已证明是错误的 上下两种写法效果是一样的)
于是则有最终版:
getDictList(this.dictIdList).then(res=>{
// console.log('字典id集合',this.dictIdList)
if(res.data.code == 20000){
res.data.data.forEach((item,index)=>{
//对字典进行处理 方便后续选择框进行选项绑定
item.sysDictItemList = item.sysDictItemList.map(item => {
return { dic_name: item.dictItemName, dic_id: item.dictItemId, no: item.dictItemId };
});
if (index == res.data.data.length -1) {
this.$set(this.useDict,item.dictId,item.sysDictItemList) // 用$set 加数据是响应式的 会带动更新节点
}else{
this.useDict[item.dictId] = item.sysDictItemList // 用赋值数据是非响应式的 赋值到最后一个数据再进行节点更新
}
})
console.log( this.useDict,'方法一的字典集合')
// this.$forceUpdate() // 更新视图后更新表单 来获取字典
}else{
this.$message({
type: "warning",
message: "查询字典集接口报错"+ res.data.msg,
});
}
})
等循环到最后一个字典再触发VUE的视图更新,自我感觉可能?运行效率会更高一些,由于现在还不清楚如何测量前端页面的渲染效率,故而这段学习暂时停到这种程度
.2021 0518 :
破案了 官方文档 对VUE响应式深入理解的后续有介绍
异步更新队列
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
来自VUE官方文档
3.将用到的字典值作为key将useDict对象写在data{} 里面,形如:
useDict:{
1000:[],
1001:[],
1002:[],
1003:[],
1004:[],
1005:[],
1006:[],
}
将视图更新所需要用到的字典id直接写到data里
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
//vm.a
是响应式的
vm.b = 2
//vm.b
是非响应式的
//来自VUE官方文档
直接将需要取到的字典ID项直接写在data 里,这样直接用等号赋值,如下:
this.useDict[item.dictId] = item.sysDictItemList
也能使得视图层监听到数据字典集合的变化从而更新视图了