第六单元(vue的实例和组件-vue实例的相关属性和方法-解释vue的原理-创建vue的组件)
#课程目标
- 掌握vue实例的相关属性和方法的含义和使用
- 了解vue的数据响应原理
- 熟悉创建组件,了解全局组件与局部组件的区别,掌握组件的相关注意事项
#知识点
#1.vue实例的相关属性和方法ß
#1.1 属性
Vue实例就是通过new Vue()得到的对象。 我们可以在先在控制台中打印一下vue的实例,如图:
- app.$data 对应组件中data的值
- app.$props 对应组件中props的值
- app.$el vue实例挂载到的节点
- app.$options 用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处
- app.$parent 父实例,如果当前实例有的话。
- app.$root 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
- app.$children
<item><div></div></item>
item的$children就是div - app.$slots 用来访问被插槽分发的内容
- app.$scopedSlots 用来访问作用域插槽
- app.$refs 一个对象,持有注册过
ref
特性 的所有 DOM 元素和组件实例。(用于所有添加过ref属性的元素) - app.$isServer 当前 Vue 实例是否运行于服务器
- app.$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (
class
和style
除外)。 - app.$listeners 包含了父作用域中的 (不含
.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
在这里主要看这4个属性:
- app.$el ===> vue实例挂载到的节点
- app.$data ===> 对应组件中data的值
- app.$options ===> 用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处
- app.$refs ===> 用于所有添加过ref属性的元素
app.$options方法实例:
var app = new Vue({
el: "#app",
data:{
msg:"hello vue!"
},
name:"xiaoming",
age:23,
showMe:function(){
console.log("自定义showMe方法");
}
})
console.log(app.$el); // 获得了el的dom对象
console.log(app.$data); // 获得了data对象
console.log(app.$options); // 获得了自定义的属性对象
console.log(app.$options.name); // 获得了自定义的name,值为:xiaoming
app.$options.showMe(); // 获取了自定义的show方法,并执行值为:自定义showMe方法
app.$refs方法实例:
<div id="app">
<div class="div1" ref="divDom"></div>
<p class="p1" ref="pDom"></p>
</div>
var app = new Vue({
el: "#app",
data:{
}
})
console.log(app.$refs) // 获取到了所有带有 ref的dom标签
console.log(app.$refs.pDom) // 获取到了带有ref属性并且值为pDom的标签
console.log(app.$refs.divDom) // 获取到了带有ref属性并且值为divDom的标签
// 可以这样设置
app.$refs.pDom.style.color = "pink";
#1.2方法
而vue实例中的方法,可以展开__proto__
对象,如图,可以看见:
其中,以下几个就是vue实例中的方法($emit,$on,$off,等等的方法之后课程再介绍):
- app.$mount() 手动挂载vue实例
- app.$destroy() 用于销毁vue实例
- app.$nextTick(callback) 用于数据更改,dom更新完成后执行
- app.set(object,key,value) 动态地为对象新增一个值,并且页面上的模版会实时地动态更新渲染值
- app.$delete(object, key) 删除一个已有的属性,dom也会实时更新
- app.$watch(data, callback(newValue,oldValue), [option]) 监视数据的变化
app.$mount()===>手动挂载vue实例 有两种写法分别为:
//第一种
var vm = new Vue({
// el: "#app", // 注释了挂载到dom对象
data:{
msg:"hello vue!"
}
})
vm.$mount("#app"); // 这样也可以挂载到dom对象
//第二种
var vm = new Vue({
// el: "#app", // 注释了挂载到dom对象
data:{
msg:"hello vue!"
}
}).$mount("#app"); // 在实例最后调用$mount("需要挂载的dom");
app.$nextTick(callback) ===> 用于数据更改,dom更新完成后执行
var vm = new Vue({
// el: "#app", // 注释了挂载到dom对象
data:{
msg:"hello vue!"
}
}).$mount("#app");
vm.msg = "hello world!"; // 这里更改了数据
// 这里获取DOM的内容为:hello vue!,因为dom数据还没更新完成
console.log(vm.$refs.pDom.innerHTML);
// 这里获取DOM的内容为:hello world!,因为dom数据已经更新完成
vm.$nextTick(function(){
console.log(vm.$refs.pDom.innerHTML);
});
app.set(object,key,value) 实例用法
<div class="box" id="app">
<button @click="addFn">添加一个属性</button>
{{user.name}}
{{user.num}}
</div>
<script>
var vm = new Vue({
el: "#app",
data:{
user:{
name:"yang"
}
},
methods:{
addFn:function(){
//this.user.num = "9527"; // 页面上的模版无法渲染
//console.log(this.user); // 能打印出来,但是也页面上的模版未渲染出来
// this.$set(this.user, "num", "9527"); // 页面上的模版渲染出来了值
Vue.set(this.user, "num", "9527") //vm.$set 的全局写法
}
}
})
</script>
app.$delete 实例用法
<div class="box" id="app">
<button @click="deleteFn">删除一个属性</button>
{{user.name}}
</div>
<script>
var vm = new Vue({
el: "#app",
data:{
user:{
name:"yang"
}
},
methods:{
deleteFn: function(){
// vm.$delete(this.user, "name"); // 删除页面上已有的一个属性,dom也会实时更新
Vue.delete(this.user, "name"); // vm.$delete 全局写法
}
}
})
</script>
vm.$watch(data, callback(newValue,oldValue), [option]) 实例用法
<input type="text" v-model="msg"> // 绑定 vm.$watch 写法1
{{msg}}
<input type="text" v-model="id"> // 绑定 watch 写法2
{{id}}
<input type="text" v-model="user.name"> // 绑定 watch 写法2
{{user.name}}
var vm = new Vue({
el:"#app",
data:{
msg:"hello vue!",
id:"1001",
user:{
name:"yang"
}
},
/* watch:{ // 写法2:vue实例提供的一个选项 - 普通监视数据变化
id:function(newValue, oldValue){
console.log("id更改之后的值"+ newValue +",id更改之前的值"+ oldValue);
}
} */
watch:{ // 写法2:vue实例提供的一个选项 - 深度监视对象数据变化
user:{
handler:function(newValue, oldValue){
console.log("user更改之后的值"+ newValue +",user更改之前的值"+ oldValue);
},
deep: true
}
}
})
// 写法1:vue实例提供的$watch方法 - 普通监视数据变化
vm.$watch("msg",function(newValue, oldValue){
console.log("msg更改之后的值"+ newValue +",msg更改之前的值"+ oldValue);
})
// 写法1:vue实例提供的$watch方法 - 深度监视对象数据变化
vm.$watch("user",function(newValue, oldValue){
// 监视一个对象后,这里的newValue 和 oldValue将会指向同一指针,也就是指向了同一个值,更改前后值会一样
console.log("user更改之后的值"+ newValue +",user更改之前的值"+ oldValue);
},{
deep:true
})
#2.数据响应原理
现代主流框架均使用一种
数据=>视图
的方式,封装了繁琐的dom操作,采用了声明式编程(Declarative Programming)替代了过去的类jquery的命令式编程(Imperative Programming)。
这张图来自vue的官方文档,文档中讲:
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
总结为需要三个模块实现数据响应:
- Observer:也就是vue官方文档第一段提到的使用Object.defineProperty监听数据变化,数据变化则触发setter,并通知订阅者Watcher。
- Watcher:作为Observer和Compile之间通信的桥梁,在自身实例化时往属性订阅器(dep)里面添加自己,待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。
- Compile: 主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
#3.vue 中的组件
#3.1 组件的基本使用
注册组件就是利用Vue.component()方法,先传入一个自定义组件的名字,然后传入这个组件的配置。这种方式创建的组件也叫做全局组件。
Vue.component('mycomponent',{
template: `<div>这是一个自定义全局组件</div>`,
data () {
return {
message: 'hello world'
}
}
})
如上方式,就已经创建了一个自定义组件,然后就可以在Vue实例挂在的DOM元素中使用它。
直接使用Vue.component()创建的组件,所有的Vue实例都可以使用。还可以在某个Vue实例中注册只有自己能使用的组件。也叫做局部组件。
var app = new Vue({
el: '#app',
data: {
},
components: {
'my-component': {
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
}
}
})
具体对比实例:
<div id="app1">
<!—全局组件的使用-->
<mycomponent></mycomponent>
<!—局部组件的使用-->
<my-component></my-component>
</div>
<div id="app2">
<!—全局组件的使用-->
<mycomponent></mycomponent>
<!—局部组件的使用 此处的使用会报错-->
<my-component></my-component>
</div>
<script>
//定义全局组件 每个Vue实例中都能使用
Vue.component('mycomponent',{
template: `<div>这是一个自定义全局组件</div>`,
data () {
return {
message: 'hello world'
}
}
})
var app1 = new Vue({
el: '#app1',
data: {
},
components: {
'my-component': {
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
}
}
})
var app2 = new Vue({
el: '#app2',
data: {
}
})
</script>
#3.2 template模板的要求
注意:组件的模板只能有一个根元素。下面的情况是不允许的。
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>
<button>hello</button>`,
#3.3 组件中的data必须是函数
可以看出,注册组件时传入的配置和创建Vue实例差不多,但也有不同,其中一个就是data属性必须是一个函数。
这是因为如果像Vue实例那样,传入一个对象,由于JS中对象类型的变量实际上保存的是对象的引用,所以当存在多个这样的组件时,会共享数据,导致一个组件中数据的改变会引起其他组件数据的改变。
而使用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就不会出现共享数据的问题来了。
#3.4 关于DOM模板的解析
当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素<ul>,<ol>,<table>,<select>
限制了能被它包裹的元素,而一些像 <option>
这样的元素只能出现在某些其它元素内部。
通俗点说,虽然 vue 渲染页面可以自定义,非常强大,但是他一定遵从遵循我们的浏览器正常解析,html 正常规范。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table>
<my-row>...</my-row>
</table>
自定义组件 被认为是无效的内容,因此在渲染的时候会导致错误。
这时应使用特殊的 is 属性:
<table>
<tr is="my-row"></tr>
</table>
又或者是select这样的:
<select>
<my-component></my-component>
</select>
select 内置固定标签 option, 所以不能随意渲染
也就是说,标准HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在于特定的父元素中。比如table中不能放置div,tr的父元素不能div等。所以,当使用自定义标签时,标签名还是那些标签的名字,但是可以在标签的is属性中填写自定义组件的名字。
#4.属性Props
Vue组件通过props属性来声明一个自己的属性,然后父组件就可以往里面传递数据。
Vue.component('mycomponent',{
template: '<div>这是一个自定义组件,父组件传给我的内容是:{{myMessage}}</div>',
props: ['myMessage'],
data () {
return {
message: 'hello world'
}
}
})
然后调用该组件
<div id="app">
<mycomponent my-message="hello"></mycomponent>
</div>
注意,由于HTML特性是不区分大小写的,所以传递属性值时,myMessage应该转换成 kebab-case (短横线隔开式)my-message="hello"。
#授课思路
#案例和作业
使用vue完成一个tab切换