1. 表单绑定 v-model
表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
Vue
中使用v-model
指令来实现表单元素和数据的双向绑定。
1.1 案例分析
当我们在输入框输入内容时。
因为
input
中的v-model
绑定了message
,所以会实时将输入的内容传递给message
,message
发生改变。当
message
发生改变时,因为上面我们使用Mustache
语法,将message的值插入到DOM
中,所以DOM
会发生响应的改变。所以,通过
v-model
实现了双向的绑定。
<div id="app">
<!-- v-model 是一個双向绑定 -->
<input type="text" v-model="message">
<h2>{{message}}</h2>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
methods: {}
});
1.2 v-model 原理
-
v-model
指令就是v-click:input
和v-bind:value
指令的结合,再使用$event
中target
获取到当前输入框中输入的值。
<div id="app">
<!--
v-model = v-on:input + v-bind:value
-->
<input type="text" name="username" id="username" :value="message" @input="valueChange">
<input type="text" name="email" id="email" :value="message" @input="message = $event.target.value">
<h2>{{message}}</h2>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
message: '您好啊!'
},
methods: {
valueChange(event) {
this.message = event.target.value;
}
}
});
1.3 v-model 结合 radio 类型
- 单选框的实现需要多个
input
使用相同的name
属性。
<div id="app">
<label for="male">
<input type="radio" name="sex" id="male" value="1" v-model="sex">男
</label>
<label for="female">
<input type="radio" name="sex" id="female" value="0" v-model="sex">女
</label>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
sex: 1
}
});
1.4 v-model 结合CheckBox
<div id="app">
<input type="checkbox" value="篮球" name="basketball" id="basketball" v-model="hobbies">篮球
<input type="checkbox" value="羽毛球" name="badminton" id="badminton" v-model="hobbies">羽毛球
<input type="checkbox" value="毽子" name="shuttlecock" id="shuttlecock" v-model="hobbies">毽子
<input type="checkbox" value="足球" name="football" id="football" v-model="hobbies">足球
<h2>{{hobbies}}</h2>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
hobbies: []
}
});
1.5 v-model 结合select
-
select
可以实现单选和多选 。使用multiple
属性。
<div id="app">
<!-- 1. 选择一个 -->
<select name="fruit" id="fruit" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="芒果">芒果</option>
<option value="李子">李子</option>
</select>
<h2>{{fruit}}</h2>
<br>
<br>
<br>
<!-- 2. 选择多个 multiple 属性-->
<select name="fruit" id="fruits" multiple v-model="fruits">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="芒果">芒果</option>
<option value="李子">李子</option>
</select>
<h2>{{fruits}}</h2>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
fruit: '香蕉',
fruits: []
}
});
1.6 v-model 的值绑定
- 所谓的值绑定就是将数据动态的绑定到每一个操作项上。
<div id="app">
<label v-for="hobby in hobbies" :for="hobby">
<input type="checkbox" name="hobby" :id="hobby" :value="hobby" v-model="selected">{{hobby}}
</label>
<h2>您的爱好是 : {{selected}}</h2>
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
hobbies: ['篮球', '足球', '羽毛球', '乒乓球', '高尔夫球'],
selected: []
}
});
1.7 v-model 修饰符的使用
lazy
: 可以让双向绑定的元素,减少实时渲染次数,只有当鼠标失去焦点或者当用户按下回车的时候才渲染。number
:可以将绑定的数据的数据类型转换为number
类型,方便一些需要使用数字的地方。trim
: 去除字符串两边的空格。
<div id="app">
<!-- 1. 使用lazy修饰符的v-model 当输入完成之后敲回车或者是当鼠标失去焦点的时候更新数据 -->
<input type="text" name="username" id="username" v-model.lazy="username">
<h2>您输入的用户名是 : {{username}}</h2>
<!-- 2. 使用number修饰符修饰的v-model 文本框中的数据类型会转换为string 后期不方便处理使用number修饰符的文本框文本类型是 number 的 -->
<input type="number" name="phoneNum" id="phoneNum" v-model.number="phoneNum">
<h2> 文本框的类型 : {{typeof phoneNum}} </h2>
<!--3. 使用trim 去除字符串两边的空格-->
<input type="text" name="content" id="content" v-model.trim="content">
<h2>{{content}}</h2>
</div>
const app = new Vue({
el: '#app',
data: {
username: '',
phoneNum: 0,
content: ''
}
});
2. 组件化
- 组件化是
Vue.js
中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树。
2.1 注册组件的基本步骤
创建组件构造器;
注册组件;
使用组件。
<div id="app">
<cpn></cpn>
</div>
<script src="../../js/vue.js"></script>
// 1. 创建组件构造器 使用Vue.extend 创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h1>我是标题</h1>
<p>我是段落一</p>
<p>我是段落二</p>
</div>
`
});
// 2. 注册组件
Vue.component('cpn', cpnC);
// 3. 挂载 app
const app = new Vue({
el: '#app',
data: {}
});
2.2 全局组件和局部组件
全局组件是使用
Vue.component('组件名称' , 组件构造器)
注册的组件;局部组件时在 ,
Vue({})
对象中使用components
属性声明的组件。
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpna></cpna>
<hr>
</div>
<div id="app2">
<cpn></cpn>
<!-- app注冊的局部组件在app中不能使用 <cpna></cpna>-->
</div>
<script src="../../js/vue.js"></script>
/**
* 全局组件可以在多个Vue的实例下面使用
*/
// 1. 使用Vue.extend 创建一个组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是段落1</p>
<p>我是段落2</p>
</div>>
`
});
/**
* 局部组件构造器声明
*/
const cpnCa = Vue.extend({
template: `
<div>
<h2>我是局部组件的标题</h2>
<p>我是段落1</p>
<p>我是段落2</p>
</div>>
`
});
// 2. 将组件注册为全局组件
Vue.component('cpn', cpnC);
const app = new Vue({
el: '#app',
data: {},
components: {
cpna: cpnCa
}
});
const app2 = new Vue({
el: '#app2',
data: {},
components: {}
});
2.3 父组件和子组件
- 组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。
2.4 注册组件的语法糖简写
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
<cpn3></cpn3>
<cpn4></cpn4>
<hr>
</div>
<div id="app2">
<cpn1></cpn1>
<cpn2></cpn2>
<cpn3></cpn3>
<cpn4></cpn4>
</div>
<script src="../../js/vue.js"></script>
/**
* (一) 传统方式注册组件
*/
// 1. 创建组件构造器
const cpnC1 = Vue.extend({
template: `
<div>
<h1>我是全局组件一</h1>
</div>
`
});
/**
* 注册一个全局组件
* */
Vue.component('cpn1', cpnC1);
/**
* (二) 使用语法糖的方式注册组件 : 主要是省去调用Vue.extend的步骤,而是可以直接使用一个对象来代替
*/
Vue.component('cpn2', {
template: `
<div>
<h1>我是全局组件二(使用语法糖方式创建)</h1>
</div>
`
});
/**
* 创建一个组件构造器用于局部组件
*/
const cpnC4 = Vue.extend({
template: `
<div>
<h1>我是局部组件四</h1>
</div>
`
});
/**
* 挂载app
*/
const app = new Vue({
el: '#app',
data: {},
components: {
/* 创建局部组件 */
cpn3: {
template: `
<div>
<h1>我是局部组件三(使用语法糖方式创建)</h1>
</div>
`
},
cpn4: cpnC4
}
});
/**
* 挂载app2
*/
const app2 = new Vue({
el: '#app2',
data: {}
});
2.5 组件模板抽离
- 在编写组件模板的时候可以使用,
script
或者template
标签定义标签模板。将其从对象中抽离出来。
<div id="app">
<cpn></cpn>
<cpn1></cpn1>
</div>
<!-- 1. 抽离template模板 -->
<script type="text/x-template" id="cpn">
<div>
<h1>我是抽离的标签的标题</h1>
<p>我是一个段落</p>
</div>
</script>
<!-- 2. 使用template标签包裹-->
<template id="cpn1">
<div>
<h1>我是抽离的标签的标题使用template标签</h1>
<p>我是一个段落</p>
</div>
</template>
<script src="../../js/vue.js"></script>
Vue.component('cpn', {
template: '#cpn'
});
Vue.component('cpn1', {
template: '#cpn1'
});
const app = new Vue({
el: '#app',
data: {}
});
2.6 组件中可以访问Vue实例中的数据吗?
组件是一个单独功能模块的封装:这个模块有属于自己的
HTML
模板,也应该有属性自己的数据data
。答案:不能。而且即使可以访问,如果将所有的数据都放在
Vue
实例中,Vue
实例就会变的非常臃肿。结论:
Vue
组件应该有自己保存数据的地方。
2.7 组件自己的数据存放在哪里呢?
组件对象也有一个
data
属性(也可以有methods等属性)。只是这个data属性必须是一个函数
。而且这个函数返回一个对象,对象内部保存着数据。
<template id="cpn">
<div>
<h1>当前计数: {{counter}}</h1>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<template id="com">
<div>
<h1>{{title}}</h1>
</div>
</template>
<div id="app">
<!-- 组件实例 -->
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<com></com>
</div>
<script src="../../js/vue.js"></script>
/**
* 语法糖方式注册组件
*/
Vue.component('cpn', {
template: '#cpn',
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
});
/**
* 使用语法糖方式注册组件
*/
Vue.component('com', {
template: '#com',
data() {
return {
title: '我是标题'
};
}
});
const app = new Vue({
el: '#app',
data: {}
});
2.8 为什么组件中的data是一个函数
- 如果不同组件他们共享一个共同的对象将会出大问题。
const obj = {
name: '张三',
age: 23
}
/**
* 每次调用函数返回一个对象
*/
function data() {
return obj;
}
let obj1 = data();
obj1.name = '李四';
let obj2 = data();
console.log(obj1); // {name: "李四", age: 23}
console.log(obj2); // {name: "李四", age: 23}
2.9 父子组件之间的通信
- 在开发中,往往一些数据确实需要从上层传递到下层,比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
如何进行父子组件间的通信呢?Vue官方提到
父组件向子组件传递数据 : 通过
props
向子组件传递数据。子组件向父组件传递数据: 通过事件向父组件发送消息。
props 的基本用法
- 在组件中,使用选项
props
来声明需要从父级接收到的数据。props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
我们先来看一个最简单的props传递:
在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。
- 真实的开发中,
Vue
实例和子组件的通信和父组件和子组件的通信过程是一样的。
实现父组件向子组件传递数据的步骤如下 :(使用字符串数组的方式)
第一步: 在子组件将
props
定义为一个字符串数组用于接收父组件传递的数据。第二步:将父组件的值和子组件定义的属性进行绑定。
第三步:值已经传递过来了,在子组件中使用父组件的数据。
<!-- 父子组件之间的通信
1. 父组件向子组件传通过props;
2. 子组件向父组件传通过自定义事件;
在真实开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
-->
<div id="app">
<!-- 第二步 将父组件的值和子组件定义的属性进行绑定 -->
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<!-- 第三步 使用子组件中的属性 -->
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../../js/vue.js"></script>
/* 父传子通过 props */
const cpn = {
template:'#cpn',
data() {
return{
}
},
// 字符串数组写法
// 第一步
props:['cmovies','cmessage']
}
const app = new Vue({
el: '#app',
data: {
message:'我是父组件的message',
movies:['陆小凤传奇之决战紫禁之巅','陆小凤传奇之剑神一笑','陆小凤传奇之凤舞九天']
},
components: {
/* 使用属性的增强写法 */
cpn
}
});
实现父组件向子组件传递数据的步骤如下 :(使用对象的方式)
props
除了使用数组的方式之外还能使用对象的方式接收父组件传递的数据。当需要对props
进行类型验证的时候,就需要使用对象的写法。对象中属性非自定义的数据类型可以使用
type
属性验证该属性的数据类型,使用required
标识该属性是否为必须传递的,使用default
属性设置默认值。
验证都支持如下类型:
String
;Number
;Boolean
;Array
:当是数组类型的时候,在设置默认值时需要注意,在某些版本之后不能使用default:[]
这样的默认值,而是需要一个函数,在函数中返回数组的默认值 ,如 :default:() => { return []; }
或default:() => []
或者default() {return [];}
;Object
;Date
;Function
;Symbol
;当我们有自定义构造函数时,验证也支持自定义的类型。
父组件传递值给子组件props属性驼峰标识
- 在进行父组件传递数据给子组件的时候,在
props
中的属性如果使用了驼峰标识,那么在子组件标签中使用该属性的时候就需要使用横线分隔的形式
,绑定父组件传递的数据。
2.10 子组件向父组件传递数据
子组件向父组件传递数据的步骤
- 第一步 : 定义一个,比如按钮事件 将子组件的数据传递给父组件。
<!-- 第一步 : 定义一个,比如按钮事件 将子组件的数据传递给父组件 -->
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
- 第二步 : 将子组件的自定义事件发射给父组件 并传递携带的参数。
btnClick(item) {
/* 第二步 : 将子组件的自定义事件发射给父组件 并传递携带的参数 */
this.$emit('customevent', item);
}
- 第三步 : 在父组件中处理子组件自定义的事件。
<!-- 第三步 : 在父组件中处理子组件自定义的事件 -->
<cpn @customevent="getSonCpnData"></cpn>
- 第四步 : 处理触发子组件自定义事件之后需要执行的方法。
/* 第四步 : 处理触发子组件自定义事件之后需要执行的方法 */
getSonCpnData(item) {
console.log(item);
}
<div id="app">
<!-- 第三步 : 在父组件中处理子组件自定义的事件 -->
<cpn @customevent="getSonCpnData"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<!-- 第一步 : 定义一个,比如按钮事件 将子组件的数据传递给父组件 -->
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="../../js/vue.js"></script>
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 1, name: '手机数码'},
{id: 2, name: '家用电器'},
{id: 3, name: '电脑办公'},
{id: 4, name: '生鲜蔬菜'}
]
}
},
methods: {
btnClick(item) {
/* 第二步 : 将子组件的自定义事件发射给父组件 并传递携带的参数 */
this.$emit('customevent', item);
}
}
};
/* 子组件产生了一些事件希望父组件知道 */
const app = new Vue({
el: '#app',
components: {
cpn
},
methods: {
/* 第四步 : 处理触发子组件自定义事件之后需要执行的方法 */
getSonCpnData(item) {
console.log(item);
}
}
});
2.11 父子组件通信-结合双向绑定
需求1: 当子组件中的
data
数据dNumber1 dNumber2
改变的时候 子组件props
中的number1 number2
也需要发生改变,利用的是v-model
的原理 是由v-on:input
和v-bind:value
组合。需求2: 当
number1
的值改变的时候 将number2
的值变为number1
的100
倍。
<div id="app">
<cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dNumber1}}</h2>
<input type="number" @input="dnumber1change" :value="dNumber1">
<h2>props:{{number2}}</h2>
<h2>data:{{dNumber2}}</h2>
<input type="number" @input="dnumber2change" :value="dNumber2">
</div>
</template>
<!--
1. 需求1: 当子组件中的 data数据 dNumber1 dNumber2 改变的时候 子组件 props 中的 number1 number2 也需要发生改变
1.1 利用的是 v-model的原理 是由 v-on:input 和 v-bind:value 组合
2. 需求2: 当 number1 的值改变的时候 将 number2 的值变为 number1 的 100 倍
-->
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
num1: 0,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value);
},
num2change(value) {
this.num2 = parseFloat(value);
}
},
components: {
'cpn': {
template: '#cpn',
props: {
/* 这里的值有父组件的 num1 或 num2 决定 */
number1: Number,
number2: Number
},
data() {
return {
dNumber1: this.number1,
dNumber2: this.number2
};
},
methods: {
dnumber1change(event) {
// 从event中获取当前改变的值
this.dNumber1 = event.target.value;
// 发送一个自定义事件给父组件让父组件的值发生改变
this.$emit('num1change', this.dNumber1);
this.dNumber2 = this.dNumber1 * 100;
this.$emit('num2change', this.dNumber2);
},
dnumber2change(event) {
this.dNumber2 = event.target.value;
this.$emit('num2change', this.dNumber2);
this.dNumber1 = this.dNumber2 / 100;
this.$emit('num1change', this.dNumber1);
}
}
}
}
});
2.12 父子组件通信-结合双向绑定 (watch实现)
<div id="app">
<cpn
:number1="num1"
:number2="num2"
@num1change="num1Change"
@num2change="num2Change"></cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{pNumber1}}</h2>
<input type="number" v-model="pNumber1">
<!--<input type="text" :value="pNumber1" @input="number1Change">-->
<h2>props:{{number2}}</h2>
<h2>data:{{pNumber2}}</h2>
<input type="number" v-model="pNumber2">
<!--<input type="text" :value="pNumber2" @input="number2Change">-->
</div>
</template>
<script src="../../js/vue.js">
const app = new Vue({
el: '#app',
data: {
num1: 0,
num2: 0
},
methods: {
num1Change(number1) {
this.num1 = parseFloat(number1);
},
num2Change(number2) {
this.num2 = parseFloat(number2);
}
},
components: {
'cpn': {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
pNumber1: this.number1,
pNumber2: this.number2
}
},
methods: {},
/* 用于监视一个属性的改变 */
watch: {
pNumber1(newValue, oldValue) {
this.pNumber2 = newValue * 100
// 子组件发送一个自定义事件给父组件
this.$emit('num1change', newValue);
},
pNumber2(newValue, oldValue) {
this.pNumber1 = newValue / 100
this.$emit('num2change', newValue);
}
}
}
}
});
watch 的使用
-
watch
: 是属于Vue
实例的一个options
。参考文档,它可以监控一个属性的变化,在watch
中可以将属性名称作为方法名称属性名称(newValue,oldValue)
,newValue
参数就是该属性改变之后的值。watch
可以监控data
中属性的变化。
<div id="app">
<input type="text" v-model="number">
</div>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {
number: 123
},
watch: {
number(newValue, oldValue) {
console.log('number从' + oldValue + '变为了' + newValue);
}
}
});
2.13 父组件直接访问子组件使用 $children
和 $refs
-
$children
获取到的是父组件中的所有子组件,結果是一个数组。包含所有子组件对象。$children
的缺陷:通过$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
-
$refs
获取到的是一个类似Map
集合的对象。但是使用$refs
获取之前需要在组件标签上使用ref
属性指定。$refs
和ref
指令通常是一起使用的。首先,我们通过ref
给某一个子组件绑定一个特定的ID
。其次,通过this.$refs.ID
就可以访问到该组件了。
<cpn ref="aaa"></cpn>
<cpn ref="bbb"></cpn>
<div id="app">
<cpn ref="aaa"></cpn>
<cpn ref="bbb"></cpn>
<button @click="btnClick">点我</button>
</div>
<template id="cpn">
<div>
<h2>{{message}}</h2>
</div>
</template>
<script src="../../js/vue.js"></script>
/* $children 可以拿到所有的子组件 */
/* $refs 拿到指定 ref 的组件 */
const app = new Vue({
el: '#app',
data: {},
methods: {
btnClick() {
// console.log(this.$children);
// console.log(this.$children[0].message);
// this.$children[0].showMessage();
console.log(this.$refs);
console.log(typeof this.$refs);
// let map = new Map();
// map.set('aaa', {name: '张三'});
// console.log(map);
console.log(this.$refs.aaa.message);
this.$refs.bbb.showMessage();
}
},
components: {
'cpn':
{
template: '#cpn',
data() {
return {
message: '我是子组件'
}
},
methods: {
showMessage() {
console.log(this.message);
}
}
}
}
});
2.14 子组件中访问父组件 $parent
和 $root
$parent
: 可以获取组件的父组件。$root
: 访问的是根组件,Vue
实例。
<div id="app">
<cpn></cpn>
<hr>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<button @click="btnClick"> cpn的按钮</button>
<hr>
<!-- 这是定义的一个局部子组件 -->
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是cpn的子组件ccpn</h2>
<button @click="btnCcpnClick">ccpn的按钮</button>
</div>
</template>
<script src="../../js/vue.js"></script>
const app = new Vue({
el: '#app',
data: {},
methods: {},
components: {
'cpn': {
template: '#cpn',
data() {
return {}
},
methods: {
btnClick() {
console.log(this.$parent); //
}
},
components: {
'ccpn': {
template: '#ccpn',
methods: {
btnCcpnClick() {
console.log(this.$parent); // 获取的是其父组件对象
console.log(this.$root); // 获取的是根组件对象
}
}
}
}
}
}
});
子组件通过$parent
访问父组件注意事项
尽管在Vue开发中,我们允许通过
$parent
来访问父组件,但是在真实开发中尽量不要这样做。子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过
$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。