最近 Ant Design vue 作者 - 唐金州, 在某平台开课了, 在整个课程中系统的讲述了 Vue 的开发实战. 在第八讲中介绍了 Vue 双向绑定的问题, 这里我整理一些资料客观的分析一下 Vue 数据响应原理到底是不是双向绑定.
很多同学在理解 Vue 的时候都把 Vue 的数据响应原理理解为双向绑定, 但实际上这是不准确的, 我们之前提到的数据响应, 都是通过数据的改变去驱动 DOM 视图的变化, 而双向绑定除了数据驱动 DOM 外, DOM 的变化反过来影响数据, 是一个双向关系, 在 Vue 中, 我们可以通过 v-model 来实现双向绑定.
在 Vue 中体现出双向绑定作用的方式有两种, 在分析之前我们先介绍这两种使用方式有什么区别.
1)v-model 属性
2).sync 修饰符
v-model
2.2.0+ 新增
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件, 但是像单选框, 复选框等类型的输入控件可能会将 value 特性用于不同的目的. model 选项可以用来避免这样的冲突:
ChildBox.vue
- <template>
- <input type="checkbox"
- :checked="checked"
- @change="$emit('change', $event.target.checked)"/>
- </template>
- <script>
- export default {
- model: {
- prop: 'checked',
- event: 'change'
- },
- props: {
- checked: Boolean
- }
- }
- </script>
Index.vue
- <template>
- <div>
- <ChildBox v-model="val"/>
- <!-- 解析后 -->
- <ChildBox :value="val"
- @change="data => (val = data)"/>
- </div>
- </template>
分析一下上面的代码. ChildBox.vue 文件对 checkbox 做了简单的封装, 提供了 checked 参数. Index.vue 文件为父组件, 引用了 ChildBox 作为自己的子组件, 这里需要注意一下. val 值的绑定使用的 v-model 而并不是 v-bind:checkbox. 一开始我们有说到双向绑定方式有两种一种是 v-model, 另一种是. sync(这个后面讲). 如果使用 v-model, 子组件的 props 应该设置 value 值, 而向上传递应该为 $emit('input') 才对. 所以这里还有一个重点, model 的作用.
model
2.2.0 新增
允许一个自定义组件在使用 v-model 时定制 prop 和 event. 默认情况下, 一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event, 但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的. 使用 model 选项可以回避这些情况产生的冲突.
.sync 修饰符
2.3.0+ 新增
在有些情况下, 我们可能需要对一个 prop 进行 "双向绑定". 不幸的是, 真正的双向绑定会带来维护上的问题, 因为子组件可以修改父组件, 且在父组件和子组件都没有明显的改动来源.
这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之. 举个例子, 在一个包含 title prop 的假设的组件中, 我们可以用以下方法表达对其赋新值的意图:
ChildBox.vue
- <template>
- <input type="checkbox"
- :checked="checked"
- @change="$emit('update:checked', $event.target.checked)"/>
- </template>
- <script>
- export default {
- props: {
- checked: Boolean
- }
- }
- </script>
Index.vue
- <template>
- <div>
- <ChildBox :checked.sync="val"/>
- <!-- 解析后 -->
- <ChildBox :checked="val"
- @update:checked="data => (val = data)"/>
- </div>
- </template>
分析一下上面的代码有什么变化, 父组件 v-model 被 :checked.sync 替换掉. 子组件因不适用 v-model, 所以不需要 model 配置. change 函数改为
event.target.checked).
v-model 源码分析
借助 ustbhuangyi vue.js 技术揭秘. 这里只做总结比对, 详细分析过程可查看链接.
以下面代码为例:
- let vm = new Vue({
- el: '#app',
- template: '<div>'
- + '<input v-model="message"placeholder="edit me">' +
- '<p>Message is: {{ message }}</p>' +
- '</div>',
- data() {
- return {
- message: ''
- }
- }
- })
在 input 元素上设置了 v-model 属性, 绑定了 message, 当我们在 input 上输入内容时, message 也会同时发生变化.
源码 generate 函数:
- function generate (
- ast,
- options
- ) {
- var state = new CodegenState(options);
- var code = ast ? genElement(ast, state) : '_c("div")';
- return {
- render: ("with(this){return" + code + "}"),
- staticRenderFns: state.staticRenderFns
- }
- }
对我们的例子而言, 最终生成的 render 代码如下:
- with(this) {
- return _c('div',[_c('input',{
- directives:[{
- name:"model",
- rawName:"v-model",
- value:(message),
- expression:"message"
- }],
- attrs:{"placeholder":"edit me"},
- domProps:{"value":(message)},
- on:{"input":function($event){
- if($event.target.composing)
- return;
- message=$event.target.value
- }}}),_c('p',[_v("Message is:"+_s(message))])
- ])
- }
最终转化为:
- <input
- v-bind:value="message"
- v-on:input="message=$event.target.value">
动态绑定了 input 的 value 指向了 messgae 变量, 并且在触发 input 事件的时候去动态把 message 设置为目标值, 这样实际上就完成了数据双向绑定了, 所以说 v-model 实际上就是语法糖.
ustbhuangyi 在 Vue.JS 技术揭秘中也详细的介绍 v-model 在组件中的实现原理, 这里就不过多的陈述了.
总结
我们了解到 v-model 是 Vue 双向绑定的真正实现, 但本质上就是一种语法糖, 它即可以支持原生表单元素, 也可以支持自定义组件. 在组件的实现中, 我们是可以配置子组件接收的 prop 名称, 以及派发的事件名称.
最后有一个问题 v-model 和 .sync 都可以实现数据双向绑定的效果, 那到底用哪种更合理呢? 欢迎回复阐述你的观点.
祝学习进步
邓文斌
2019 年 3 月 21 日
来源: https://juejin.im/post/5c9351135188252d9b376152