一、v-model
1、v-model的含义
v-model就是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性时候,更新页面上输入控件的值。
2、v-model的基础用法
(1)v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
(2)v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
(3)v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
3、使用示例
当你在input输入框中输入内容的时候,上面p标签里面的数据实时发生变化(实际上是name和age两个数据发生了响应式变化)。相反改变代码中的name和age值,input输入框中值也会实时变化。
其实现原理如下:
<div id="app">
<p>姓名:<input :value="name" @input="updateName">{{name}}</p>
<!-- v-model 其实就是v-bind: 和 v-on: 的语法糖 -->
<p>年龄:<input :value="age" @input="updateAge">{{age}}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
name: '张三',
age: 20,
},
methods: {
updateName(e) {
this.name = e.target.value;
},
updateAge(e){
this.age = e.target.value;
}
},
})
</script>
实现效果如下:
实现原理:
①首先input输入框通过属性绑定:value="name"&:value="age"得到响应数据name&age.
②定义两个函数,通过e.target得到input框中的value值。
③最后通过input输入框@input事件监听,绑定两个函数(updateName,updateAge),将input框中的value值传给name&age。
vue中的v-model能够实现数据的双向绑定,也是vue的最突出的优势。
v-model实际上是v-bind: 和 v-on:的语法糖。它的实现原理主要包括属性绑定和事件监听两部分。
具体使用如下:
<div id="app">
<p>姓名:<input v-model="name">{{name}}</p>
<!-- v-model 其实就是v-bind: 和 v-on: 的语法糖 -->
<p>年龄:<input v-model="age">{{age}}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
name: '张三',
age: 20,
},
})
</script>
实现效果与v-bind: + v-on:相同:
二、sync修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。但真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件两侧都没有明显的变更来源。
当我们需要在某个标签中绑定多个属性时,就选择使用.sync修饰符。
.sync修饰符的约定:
① 属性绑定必须是xx.sync
② 自定义事件必须是update:xx格式
③ 采用xx.sync修饰符,可以省略update:xx对应的事件绑定
使用方法如下:
<div id="app">
<div>
衣服:{{yf}},裤子:{{kz}},鞋子:{{xz}}
</div>
<hr>
<!-- 绑定属性是,采用xx.sync修饰符,可以省略update:xx对应的事件绑定 -->
<!-- 约定1:属性绑定必须是xx.sync -->
<b-counter :yf.sync="yf" :kz.sync="kz" :xz.sync="xz"></b-counter>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-counter', {
template: `
<div>
<div class='conter'>
<div class='label'>衣服</div>
<div class='btns'>
<button @click='yfCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='yfCount'>
<button @click='yfCount++' class='btn'>+</button>
</div>
</div>
<div class='conter'>
<div class='label'>裤子</div>
<div class='btns'>
<button @click='kzCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='kzCount'>
<button @click='kzCount++' class='btn'>+</button>
</div>
</div><div class='conter'>
<div class='label'>鞋子</div>
<div class='btns'>
<button @click='xzCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='xzCount'>
<button @click='xzCount++' class='btn'>+</button>
</div>
</div>
</div>`,
props: ['yf', 'kz', 'xz'],
data() {
return {
yfCount: this.yf,
kzCount: this.kz,
xzCount: this.xz,
}
},
// 监听器
watch:{
yfCount(val){
// 约定2:自定义事件必须是update:xx
this.$emit('update:yf',val)
},
kzCount(val){
this.$emit('update:kz',val)
},
xzCount(val){
this.$emit('update:xz',val)
},
}
})
new Vue({
el: '#app',
data: {
// 衣服数量
yf: 5,
// 裤子数量
kz: 5,
// 鞋子数量
xz: 5
}
})
</script>
三、插槽
插槽的含义
插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。
1、匿名插槽
匿名插槽,我们又可以叫它单个插槽或者默认插槽。与具名插槽相对,它不需要设置name属性。(它隐藏的name属性为default。)
示例:
① 在vue子组件中定义一个匿名插槽
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div class="box">
<div class="item">
<h2>插槽</h2>
<slot></slot>
</div>
</div>
`
})
new Vue({
el: '#app',
})
</script>
② 在页面中使用子组件标签,并写入内容
<div id="app">
<b-box>
<!-- 如果没有slot插槽,这里的内容将不会显示 -->
<div>我是匿名插槽</div>
</b-box>
</div>
效果如图:
2、具名插槽
插槽有一个特殊的属性:name,这个属性可以用来定义多个插槽。
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。
v-slot:的简写为#
使用方法如下:
<div id="app">
<b-box>
<template v-slot:slot1>
<div>我是插槽1</div>
</template>
<template v-slot:slot2>
<div>我是插槽2</div>
</template>
<template v-slot:slot3>
<div>我是插槽3</div>
</template>
</b-box>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div class="box">
<div class="item">
<h2>插槽1</h2>
<slot name="slot1"></slot>
</div>
<div class="item">
<h2>插槽2</h2>
<slot name="slot2"></slot>
</div>
<div class="item">
<h2>插槽3</h2>
<slot name="slot3"></slot>
</div>
</div>
`
})
new Vue({
el: '#app',
})
</script>
实现效果如下:3、作用域插槽
作用域插槽其实就是可以传递数据的插槽。子组件中的一些数据想在父组件中使用,必须通过规定的方法来传递。
作用域插槽必须是具名插槽,在作用域插槽上可以通过v-bind:绑定属性,绑定的属性,通过指定的作用域变量是接收 。
绑定在元素上的 属性被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
//default可以省略,简写为v-slot=" ",slotProps是自定义的名字,
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
具体使用如下:
<div id="app">
<!-- 作用域插槽必须是具名插槽,在作用域插槽上可以通过v-bind:绑定属性,绑定的属性,通过指定的作用域变量是接收 -->
<b-box>
<template #list="scope">
<button @click="priceDown(scope.list,scope.index)">降价</button>
<button @click="priceUp(scope.list,scope.index)">加价</button>
<button @click="scope.list.splice(scope.index,1)">删除</button>
</template>
</b-box>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div>
<ul>
<li v-for="(item,index) in list" :key="index">
<span>{{item.id}}--{{item.name}}--{{item.price}}</span>
<slot name="list" v-bind:index="index" v-bind:list="list"></slot>
</li>
</ul>
</div>
`,
data() {
return {
list: [
{
id: 1001,
name: '苹果手机',
price: 6799
},
{
id: 1002,
name: '华为手机',
price: 5999
},
{
id: 1003,
name: '小米手机',
price: 1799
},
{
id: 1004,
name: '红米手机',
price: 3499
},
]
}
},
})
new Vue({
el: '#app',
methods: {
priceDown(list,index){
list[index].price-=1000
},
priceUp(list,index){
list[index].price+=1000
},
},
})
</script>
四、混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
1、选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
注意:Vue.extend() 也使用同样的策略进行合并
2、全局混入
当我们存在多个组件中的数据或者功能很相近时,我们就可以利用mixins将公共部分提取出来,在 mixin函数中混入统一的成员。
注意:请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项。推荐将其作为插件发布,以避免重复应用混入。
<div id="app1">
<p>姓名:<input type="text" v-model="name"></p>
<p>年龄:<input type="text" v-model.number="age"><button @click="age++">++</button></p>
<p>性别:<input type="text" v-model="sex"></p>
<p>税前工资:<input type="text" v-model="salary">税后工资:<input type="text" :value="salary2"></p>
<p>汽车信息:{{car}}</p>
<button @click="sayHi">sayHi</button>
<button @click="getSubjects">请求课程数据</button>
<div>
{{subjects}}
</div>
</div>
<hr>
<div id="app2">
<p>姓名:<input type="text" v-model="name"></p>
<p>年龄:<input type="text" v-model.number="age"><button @click="age++">++</button></p>
<p>性别:<input type="text" v-model="sex"></p>
<p>税前工资:<input type="text" v-model="salary">税后工资:<input type="text" :value="salary2"></p>
<button @click="sayHi">sayHi</button>
</div>
<script>
Vue.config.productionTip = false
// 给所有的vue实例混入统一的成员
Vue.mixin({
data() {
return {
name:'',
age:0,
sex:'男',
salary:1000
}
},
methods: {
sayHi(){
alert(`大家好!我叫${this.name},性别是${this.sex},今年${this.age}岁`)
},
async $get(url,params){
let {data} = await axios.get(url,{params})
return data
},
async $post(url,params){
let {data} = await axios.post(url,params)
return data
}
},
computed:{
salary2(){
return this.salary*0.8
}
},
watch:{
age(val){
if (this.age>100) {
alert('年龄不能超过100')
this.age = 100
}
}
},
mounted() {
console.log('mixin:挂载完成')
},
})
new Vue({
el:'#app1',
data:{
car:{
name:'奔驰',
price:'100W'
},
subjects:[]
},
methods: {
async getSubjects(){
let {data} = await this.$get('http://bingjs.com:81/Subject/GetSubjectsConditionPages',)
this.subjects = data
}
},
mounted() {
console.log('app1:挂载完成')
},
})
new Vue({
el:'#app2',
mounted() {
console.log('app2:挂载完成')
},
})
</script>
效果如图: