前言
这个主要的来源是 偶尔的情况下 出现的问题
就比如是 el-select 中选择组件之后, 视图不回显, 然后 model 不更新等等
这个 其实就是 vue 中 视图 -> 模型 的数据同步, 我们通常意义上的处理一般是通过 模型 -> 数据 的数据同步, 比如 我们代码里面更新了 model.sex 从 ”男” 更新为 ”女”, 然后 el-input 中对应的输入框的值也从 从 ”男” 更新为 ”女”
然后 我们这里关注的问题是, 比如 我在 el-input 将 “男” 更新为 “女”, 传递到 model.sex 的问题
我们这里主要关注的就是 v-model 和 v-bind 的具体实现
然后 这个在 以前初学的情况下, 视图 -> 模型 的同步问题
这里 来看一下 这个问题的细节
主要的传递方式为 v-model 和 v-bind, “v-bind:value“ 可以简写为 “:value”
v-model 是 视图 <-> 模型 数据双向同步的
v-bind 是 模型 -> 视图 数据单向同步的
然后 这种 视图 -> 模型 数据不同步的情况 一般是 v-bind 的情况
其他的一些情况 模型 -> 视图 数据不同步的情况, 可以参见
el-dialog 的 appendToBody 属性, 导致 vue 响应式失效
el-tree defaultCheckedKeys配置 和 树上面选中节点不同步问题
特定的操作之后响应式对象不“响应“了(一)
特定的操作之后响应式对象不“响应“了(二)
直接使用 dom api 更新了 #text节点, 之后响应式更新不生效了
测试用例
测试用例如下
<template>
<div class="testParent" >
<el-select :value="model.sex" >
<!-- <el-select v-model="model.sex" >-->
<el-option v-for="item in sexList" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
<!-- <el-input :value="model.sex" ></el-input>-->
<!-- <el-input v-model="model.sex" ></el-input>-->
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data() {
return {
model: {
sex: "1"
},
sexList: [
{code: "1", name: "男"},
{code: "2", name: "女"},
]
};
},
computed: {},
created() {
},
mounted() {
let _this = this
setInterval(function() {
_this.model.sex = "2"
console.log("--", _this.model)
}, 3000)
},
methods: {
}
};
</script>
<style>
</style>
可以看到的是 通过 v-bind 绑定的数据, 操作了 界面, 但是 界面没有更新, 界面没有更新是因为 数据没有更新
v-mode 和 v-bind 的差异
当代码为 v-bind 的时候, 我们看一下 el-select 的 模部分的处理, 只是传入了一个 value 的值
绑定数据为 v-model 的时候, 除了注册 value 的绑定之外, 还注册了一个 callback 回调
这个 callback 回调, 就是 视图 -> 模型 数据同步的一个关键的地方
视图上有数据更新的话, 视图的处理回调链路上面会增加一个这个回调, 来更新 vm.model.sex 的数据, 使得模型拿到的是 视图的最新的数据
v-model 的数据绑定 和 视图->数据处理函数的绑定
在 v-model 的情况如下
这里将 model 中的配置提取到了 props 和 事件映射 中
v-model 如果配置属性名称, 默认取 value, 如果没有配置 事件名称, 默认取 input 事件
我们这里关注的属性, 这里 props 配置为 props[value] = “1”
接下来就是, 从 attrs, props 中提取需要传递给子组件的 props 数据
这里可以看到上面的 v-model 转换之后的结果, 传入给子组件的 value 为 “1”, 注册了一个 input 的处理函数, 其中包含了 _vm.$set(_vm.model, “sex”, $$v) 的回调处理
从 attrs 和 props 中采集需要传递给子组件的属性列表, 优先从 props 中获取
v-bind 的数据绑定
这边的数据绑定是通过 attrs 来进行交互的
app.vue 上面生成了创建 el-select 的时候, 需要传入 value = “1”
这里从 attrs 中解析需要传递给子组件的 props 数据
注意 这里没有 input 的回调处理事件, 这个在 app.vue 变异之后的结果中也能看到
数据 -> 视图 的初次绑定
是在 el-select 的 mounted 中, 这里获取 value 对应的 option
然后更新 选中的对象, 选中的 label 展示给用户
这里的 selectedLabel 为 el-select 这边展示给用户的选中的数据的标签, 这个具体可以参见 el-select 的实现
el-select 中展示给用户的 输入框
数据 -> 视图 的同步绑定
这个是基于 vue 本身的响应式机制, 响应式属性更新的时候回级联更新 属性, class, dom事件, dom属性, 样式 等等
这个就在 patchVNode 里面一看就好了
同步更新 属性, class, dom事件, dom属性, 样式 等等
属性主要是指的是 dom 元素的 set/getAttribute 读写的相关数据
dom属性主要是指的是 dom 元素 本身的各个字段
注意 v-bind 虽然没有 视图 -> 数据 的同步绑定, 但是 它的 数据 -> 视图 的绑定这部分是由 vue 本身的响应式机制提供支持的, 是没有问题的
将 el-select 的 value 传入更新为 :value="model.sex", 然后 三秒之后 model.sex 在 setInterval 中更新, 然后可以看到 视图也更新了
v-model 中 视图 -> 数据 的同步绑定
整个流程是通过 vue 的 input 事件串联起来的
用户这边点击的是 select 下面的 option, 实现 option 的 dom 元素是一个 li, li 上面注册了一个 click 事件, 如下, 提交了一个 事件给父组件
select 组件这边处理的时候, 又给 父组件提交了一个 input 事件, 携带了最新的 value
select 的父组件就是 我们这边的业务组件了, 由编译的时候 注册了一个 input 事件的处理函数
这里 $$v 拿到的是 用户选择的最新的值, 然后 这里的通过 input 事件的回调 来更新了模型上面的数据
项目中可能碰到的 v-model 双向绑定失效的情况
这种情况主要的来源是 绑定的对象不是响应式对象, 因此 造成了 el-select 提交 input 事件之后, 业务组件这边虽然有 _vm.$set(_vm.model, "sex", $$v), 但是 model.sex 不是响应式对象, 造成了级联更新通知, 等等 存在问题
注意观察如下的 model.sex 属性, 是在函数中使用 model.sex 创建的属性, 这种方式创建的属性不是响应式的, 然后 导致了上面的问题, 所以 编码的时候, 视图中使用到的属性, 最好是在 data 中枚举出来, 避免一些 不必要的响应式的问题
复现的方式如下
<template>
<div class="testParent" >
<!-- <el-select :value="model.sex" >-->
<el-select v-model="model.sex" >
<el-option v-for="item in sexList" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
<!-- <el-input :value="model.sex" ></el-input>-->
<!-- <el-input v-model="model.sex" ></el-input>-->
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data() {
return {
model: {
},
sexList: [
{code: "1", name: "男"},
{code: "2", name: "女"},
]
};
},
computed: {},
created() {
},
mounted() {
let _this = this
_this.model.sex = "1"
setInterval(function() {
_this.model.sex = "2"
console.log("--", _this.model)
}, 3000)
},
methods: {
}
};
</script>
<style>
</style>
如下, model.sex 没有响应式的 setter, getter, el-select 中虽然提交了一个 input 的事件
业务组件这边 也有 _vm.$set(_vm.model, "sex", $$v) 处理, 但是这里仅仅是更新了 model.sex 的值, 没有响应式的相关级联操作
因此 造成了数据通知给 el-select 存在问题, 然后 造成了 el-select 这边, 虽然 我们点击了该 option, 但是 最终的结果就像是 没有点击一样
操作的现象如下
解决的方式, 视图中使用了 model.sex, 在 data 下面的 model 下面将 sex 枚举出来
或者 首次初始化 model.sex 的时候, 使用 $set, this.$set(this.model, "sex", "1")
完