通过上一节的学习,我们了解到了在Vue的组件中数据传递: prop 向下传递,事件向上传递 。意思是父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。但Vue中, props 是单向数据绑定,虽然在Vue 1.0版本中,通过 .sync 能实现双向数据绑定。但 .sync 在几个版本中被移除,尽管在2.3版本重新引入 .sync 修饰符,可这次引入只是作为一个编译时的语法糖存在。如果直接使用 .sync 修饰符来做双向数据绑定,会报警告信息。那么我们如何在组件中实现双向数据绑定呢?这一节我们就来学习这方面的相关知识。
实现组件双向数据绑定
在上一节中最后的示例使用的是Vue 1.0版本中的 .sync 实现数据双向绑定。那我们先来看看抛弃 .sync 修饰符来实现组件双向数据绑定的工作: 通过Vue提供的机制,绕开直接修改 prop 来实现组件双向数据绑定 。
其思路大致是这样:
在数据渲染时使用 prop 渲染数据
将 prop 绑定到子组件自身的数据上,修改数据时修改自身数据来替代 prop
watch 子组件自身数据的改变,触发事件通知父组件更改绑定到 prop 的数据
这样做的好处是: 父组件数据改变时,不会修改存储 prop 的子组件数据,只是以子组件数据为媒介,完成对 prop 的双向修改 。
继续拿上一节的示例来举例,只不过接下来的示例,并没有使用 .sync 来实现双向数据绑定的效果。
修改的代码如下:
<div> <div> <h3>父组件Parent数据</h3> <ul> <li> <label>姓名:</label> <span>{{ name }}</span> <input type="text" v-model="name" /> </li> <li> <label>年龄:</label> <span>{{ age }}</span> <input type="number" v-model="age" /> </li> </ul> </div> <child :my-name="name" :my-age="age" @update:my-name="val => name = val" @update:my-age="val => age = val"></child> </div> <template> <div> <h3>子组件child数据</h3> <ul> <li> <label>姓名</label> <span>{{ myName }}</span> <input type="text" v-model="childMyName" /> </li> <li> <label>年龄</label> <span>{{ myAge }}</span> <input type="number" v-model="childMyAge" /> </li> </ul> </div> </template>
在上面的这个示例中,我们并没有使用 .sync 修饰符,但在调用子组件的时候使用了 @update :
<child :my-name="name" :my-age="age" @update:my-name="val => name = val" @update:my-age="val => age = val"></child>
子组件中渲染到HTML模板的数据是用的 prop 数据,但监听 input 是使用的子组件自身定义的数据作为 v-model 。这样一来就不会直接修改 prop 。简单来说, 一切 prop 的改变从本质上来说都由父组件完成 。JavaScript的代码如下:
let parent = new Vue({ el: '#app', data () { return { name: 'w3cplus', age: 7 } }, components: { 'child': { template: '#child', props: ['myName', 'myAge'], data () { return { childMyName: this.myName, childMyAge: this.myAge } }, watch: { childMyName: function (val) { this.$emit('update:my-name', val) }, childMyAge: function (val) { this.$emit('update:my-age', val) } } } } })
最终效果如下:
上面的示例效果,不管是修改父组件的数据还是子组件的数据,都会相互影响:
因为子组件中 props 的 myName 和 myAge 不可写,所以在 data 中创建一个副本 childMyName 和 childMyAge 。初始值为 props 属性 myName 和 myAge 的值,同时在组件内所有需要调用 props 的地方调用 data 中的 childMyName 和 childMyAge 。
components: { 'child': { template: '#child', props: ['myName', 'myAge'], data () { return { childMyName: this.myName, childMyAge: this.myAge } }, ... } }
接下来在子组件中通过 watch 来对 props 属性的 myName 和 myAge 进行监听。当 props 修改后对应 data 中的副本 childMyName 和 childMyAge 也要同步数据。
... watch: { childMyName: function (val) { this.$emit('update:my-name', val) }, childMyAge: function (val) { this.$emit('update:my-age', val) } } ...
接下来要做的事情就是当组件内的 props 属性发生变化时,需要向组件外(父组件)发送通知,通知组件内属性变更,然后由外层(父组件)自己来决定是否变更他的数据。
接下来我们按上面的方案来改造上一节示例中的switch按钮。
至此,实现了组件内部数据与组件外部的数据的双向绑定,组件内外数据的同步。简而言之: 组件内部自已变了告诉外部,外部决定要不要变更 。
什么样的 props 适合做双向绑定