vue第六单元(vue的实例和组件-vue实例的相关属性和方法-解释vue的原理-创建vue的组件)

2022-11-01,,,,

第六单元(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切换

vue第六单元(vue的实例和组件-vue实例的相关属性和方法-解释vue的原理-创建vue的组件)的相关教程结束。

《vue第六单元(vue的实例和组件-vue实例的相关属性和方法-解释vue的原理-创建vue的组件).doc》

下载本文的Word格式文档,以方便收藏与打印。