等都不能直接使用自定义组件标签,除非指定is属性,而在4种实现模板方式中除了 模板,其他3种如字符串模板和x-template模板和.vue组件时中可以直接嵌套自定义组件标签,不用is指定。
3.9data属性
通过data属性指定自定义组件的初始数据,要求data必须是一个函数,如果不是函数就会报错。
Vue . component ( 'my- component', {
template: '< button @click = "count += 1" > 计数{ { count} } < / button> ',
data: function ( ) {
return { count: 0 }
}
} ) ;
综合例子:
< div id= "app" >
< my- component> < / my- component>
< my- component> < / my- component>
< my- component> < / my- component>
< / div>
< script>
Vue . component ( 'my- component', {
template: '< button @click = "count += 1" > 计数{ { count} } < / button> ',
data: function ( ) {
return { count: 0 }
}
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
count: 0
} ,
} ) ;
< / script>
结果
3.10props属性
组件可以嵌套使用,叫做父子组件 。那么父组件经常要给子组件传递数据这叫做父子组件通信 。父子组件的关系可以总结为 props 向下传递,事件向上传递 。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息 。看看它们是怎么工作的:
使用父组件给子组件传递属性流程:
在父组件中定义数据
在使用组件时,绑定父组件中的数据
在子组件中通过props属性声明父组件中传递过来的参数
在template属性中使用父组件中的参数
举例:父组件给子组件传递属性msg和greetText,子组件用属性a和b接收,并打印输出
< div id= "app" >
< ! -- v- bind: a 简写成 : a -- >
< child : a= "msg" : b= "greetText" > < / child>
< / div>
< script>
Vue . component ( 'child' , {
props: [ 'a' , 'b' ] ,
template: '< span> { { a} } == { { b} } < / span> '
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
msg: '来自父组件的消息',
greetText: '你好Child '
}
} ) ;
< / script>
结果展示
3.11props校验
子组件在接收父组件传入数据时, 可以进行prop校验,来确保数据的格式和是否必传。
注意点1: props可配置3种形式,如果没有参数格式化校验,推荐形式1使用居多。
形式1:简单声明接收
props: [ 'name' , 'age' , 'sex' ]
形式2:接收的同时对数据进行类型限制
props: {
name: String ,
age: Number ,
sex: String
}
形式3:接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props: {
name: {
type: String ,
required: true ,
} ,
age: {
type: Number ,
default : 99
} ,
sex: {
type: String ,
required: true
}
}
注意点2:
问题:如果data中属性名和props中属性重名,谁优先级高?
答案: props种属性名优先级高,因此为了避免不必要问题,避免data中属性名和props中属性重名
注意点3: props后面是对象而不是数组形式(即:只接收属性不校验可使用数组形式,如props:[],如果要进行校验请使用对象形式,如props:{}),同时可以指定以下属性:
type: 指定数据类型 String Number Object …注意不能使用字符串数组,只能是对象大写形式
required: 指定是否必输
default: 给默认值或者自定义函数返回默认值
validator: 自定义函数校验
形式如下:
Vue . component ( 'example', {
props: {
propA: Number ,
propB: [ String , Number ] ,
propC: {
type: String ,
required: true
} ,
propD: {
type: Number ,
default : 100
} ,
propE: {
type: Object ,
default : function ( ) {
return { message: 'hello' }
}
} ,
propF: {
validator: function ( value) {
return value > 10
}
}
}
} )
举例
完整代码:
< div id= "app" >
< child : a= "msg" : c= "greetText" : f= "hello" > < / child>
< / div>
< script>
Vue . component ( 'child' , {
props: {
'a' : String ,
'b' : [ Number , String ] ,
'c' : {
required: true
} ,
'd' : {
default : 100
} ,
e: {
type: Number ,
default : function ( ) {
return 1 ;
}
} ,
f: {
type: Number ,
validator: function ( value) {
return value < 100 ;
}
}
} ,
template: '< span> { { a} } == { { d} } == { { e} } == { { f} } < / span> '
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
msg: '来自父组件的消息',
greetText: '你好Child ',
hello: 12
}
} ) ;
< / script>
结果展示
注意点4: props是用来接收传递过来的属性值,最后会统一绑定到vc上,最好不要直接修改props的属性值(也就是不要直接修改vc上面的props接收的属性值,会报错),会报错如图,所以为了避免这个问题,最好的解决方案是在data中重新定一个新属性值,用来接收props中传递过来的参数属性 注意点5: 组件标签传递的属性名也是有限制的,不能啥都瞎传,比如你想传递key就会报错如图,报错说key已经被征用了,不让使用
< Student name= "李四" sex= "女" : age= "18" key= ”123 ’/ >
props: [ 'name' , 'age' , 'sex' , key]
3.12非props属性
标签引用子组件时,非定义的props属性,自动合并到子组件上,class和style也会自动合并,如果class或者style重复采用就近原则。
举例:定义子组件设置class和style,而标签引用子组件时也设置了class和style,那么会进行属性合并,如果重名采用就近原则。
< div id= "app" >
< child data- index= "0" class = "cont" style= "font-size: 20px;" > < / child>
< / div>
< script>
Vue . component ( 'child' , {
template: '< span class = "item" style= "color:red;" > 我是child< / span> '
} ) ;
var app = new Vue ( {
el: '#app'
} ) ;
< / script>
结果展示
3.13自定义事件
适用于:子组件 =》 给父组件传值
父组件给子组件传值使用props属性, 那么需要子组件更新父组件时,要使用自定义事件$on和$emit:
$on 监听: 不能监听驼峰标示的自定义事件, 使用全部小写(abc)或者-(a-b-c)
$emit 主动触发: $emit(事件名,传入参数)
3.13.1自定义事件绑定到子组件
注意点1:
问题:子组件调用父组件方法时传参,父组件如何接收到参数值?
1 )如果只传递一个参数,比如:this . $emit ( 'update- count', "你是谁?" ) ;
那么子组件标签形参可不带参数或者形参使用$event
< child v- on: update- count= "changeCount" > < / child>
或者< child v- on: update- count= "changeCount($event)" > < / child>
那么父组件( vue实例) 方法中通过value即可接收参数比如: changeCount: function ( value)
2 )如果传递多个参数,比如: this . $emit ( 'update- count', "ldz" , 29 ) ; ,
那么子组件标签形参请使用arguments
< child v- on: update- count= "changeCount(arguments)" > < / child>
那么父组件( vue实例) 方法中通过value[ index] 即可接收参数,比如:
changeCount: function ( value) {
console. log ( "@" + value[ 0 ] ) ;
console. log ( "@" + value[ 1 ] ) ;
有2种方式都可以实现父组件接收子组件传递的多个参数 方式1(推荐方式1,因为好记): 调用子标签不写括号和形参,但方法形参使用…
子组件
this . $emit ( 'student1Send', this . ipt, this . ipt2)
父组件
< B ref= "childrenB" @student1Send = "getB" > < / B >
methods: {
getB ( . . . args) {
this . strb = args[ 0 ] ;
this . strb2 = args[ 1 ] ;
}
方式2: 调用子标签写括号和形参arguments,那么方法形参不使用…
子组件
this . $emit ( 'student1Send', this . ipt, this . ipt2)
父组件
< B ref= "childrenB" @student1Send = "getB(arguments)" > < / B >
methods: {
getB ( args) {
this . strb = args[ 0 ] ;
this . strb2 = args[ 1 ] ;
}
注意点2: 子组件标签中形参必须叫arguments,而父组件中方法形参function(value),这个value名字可以叫任意名字
举例1:模拟只传递一个参数
1)声明父组件
var app = new Vue ( {
el: '#app' ,
data: {
count: 0
} ,
methods: {
changeCount: function ( value) {
console. log ( value) ;
this . count += 1 ;
}
}
} ) ;
2)自定义事件
< div id= "app" >
< ! -- 自定义事件update- count -- >
< child v- on: update- count= "changeCount" > < / child>
< p> { { count} } < / p>
< / div>
在事件v-on:update-count中的update-count就是自定义事件的名字,不要使用驼峰标示,html不区分大小写,会导致子元素无法主动触发父组件的自定义事件。
3)定义子组件
Vue . component ( 'child' , {
template: '< button v- on: click= "update" > 子组件Child < / button> ',
methods: {
update: function ( ) {
console. log ( 'update' ) ;
this . $emit ( 'update- count', ' 子组件参数') ;
}
}
} ) ;
子组件child中定义的update方法,内部通过$emit(‘update-count’)主动触发父元素事件的执行。
结果展示
举例2:模拟传递多个参数
子组件
< template>
< div>
< button @click = "student1Send()" > 点击学生1 组件发送消息< / button>
< / div>
< / template>
< script>
export default {
name: "B" ,
data ( ) {
return {
ipt: "我是学生1组件发过来数据" ,
ipt2: "我是你爸爸"
}
} ,
methods: {
student1Send ( ) {
this . $emit ( 'student1Send', this . ipt, this . ipt2)
} ,
}
}
< / script>
< style scoped>
< / style>
父组件
< template>
< div id= "app" >
< B ref= "childrenB" @student1Send = "getB(arguments)" > < / B >
< p> 接收的b数据:{ { strb} } < / p>
< p> 接收的b数据:{ { strb2} } < / p>
< / div>
< / template>
< script>
import B from "./B.vue"
export default {
name: "A" ,
data ( ) {
return {
strb: "" ,
strb2: "" ,
}
} ,
components: {
B
} ,
methods: {
getB ( args) {
console. log ( args) ;
this . strb = args[ 0 ] ;
this . strb2 = args[ 1 ] ;
console. log ( this . $refs. childrenB)
}
} ,
结果展示
3.13.2自定义事件挂载到父组件
自定义事件不仅可以绑定在子组件,也可以直接挂载到父组件,使用$on绑定和$emit触发。 2种方式区别: 自定义事件绑定到子组件, 其实是调用定义子组件方法 -> 调用自定义事件 -> 调用父组件方法。 自定义事件挂载到父组件, 其实是vue实例直接配置自定义函数从而达到操作父组件属性,第一步 通过app.$on主动挂载自定义函数名,主动挂载就等于vue实例定义method方法,第二步 通过app.$emit主动触发事件。
< div id= "app" >
< / div>
var app = new Vue ( {
el: '#app' ,
data: {
count: 0
}
} ) ;
app. $on ( 'update- count', function ( value) {
console. log ( value) ;
this . count += 1 ;
} ) ;
app. $emit ( 'update- count', 123 ) ;
3.13.3自定义事件-解绑
自定义事件既然可以定义绑定,那么肯定也支持解绑,所谓解绑就是让自定义事件失效。 语法:this.$off(‘自定义事件名’)
this . $off ( 'atguigu')
this . $off ( [ 'atguigu',' demo'] )
this . $off ( )
3.13.4使用自定义事件的容易出错的点
案例1:有3个组件,父组件App.vue,2个子组件Student.vue和School.vue,想实现点击子组件按钮把学生名传递给App,并在父组件App上显示出来
初始化页面
点击按钮成功收到消息图片
完整代码:
App.vue
< template>
< div class = "app" >
< h1> { { msg} } ,学生姓名是: { { studentName} } < / h1>
< ! -- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -- >
< School : getSchoolName= "getSchoolName" / >
< ! -- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v- on) -- >
< Student @atguigu = "getStudentName" / >
< ! -- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -- >
< Student ref= "student" / >
< / div>
< / template>
< script>
import Student from '. /components/ Student '
import School from '. /components/ School '
export default {
name: 'App' ,
components: { School , Student } ,
data ( ) {
return {
msg: '你好啊!' ,
studentName: ''
}
} ,
methods: {
getSchoolName ( name) {
console. log ( 'App 收到了学校名:', name)
} ,
getStudentName ( name, . . . params) {
console. log ( 'App 收到了学生名:', name, params)
this . studentName = name
} ,
} ,
mounted ( ) {
this . $refs. student. $on ( 'atguigu', ( name, . . . params) = > {
console. log ( 'App 收到了学生名:', name, params)
console. log ( "this:" , this ) ;
this . studentName = name
} )
} ,
}
< / script>
< style scoped>
. app{
background- color: gray;
padding: 5 px;
}
< / style>
School.vue
< template>
< div class = "school" >
< h2> 学校名称:{ { name} } < / h2>
< h2> 学校地址:{ { address} } < / h2>
< button @click = "sendSchoolName" > 把学校名给App < / button>
< / div>
< / template>
< script>
export default {
name: 'School' ,
props: [ 'getSchoolName'] ,
data ( ) {
return {
name: '尚硅谷' ,
address: '北京' ,
}
} ,
methods: {
sendSchoolName ( ) {
this . getSchoolName ( this . name)
}
} ,
}
< / script>
< style scoped>
. school{
background- color: skyblue;
padding: 5 px;
}
< / style>
Student.vue
< template>
< div class = "student" >
< h2> 学生姓名:{ { name} } < / h2>
< button @click = "sendStudentlName" > 把学生名给App < / button>
< / div>
< / template>
< script>
export default {
name: 'Student ',
data ( ) {
return {
name: '张三' ,
sex: '男' ,
number: 0
}
} ,
methods: {
sendStudentlName ( ) {
this . $emit ( 'atguigu', this . name, 666 , 888 , 900 )
} ,
} ,
}
< / script>
< style lang= "less" scoped>
. student{
background- color: pink;
padding: 5 px;
margin- top: 30 px;
}
< / style>
方式1: 使用v-bind标签,父组件提前把函数给子组件,子组件用props接收并调用,这样父组件函数中就能收到子组件传过来的值,只贴出重要代码
App.vue
< School : getSchoolName= "getSchoolName" / >
methods: {
getSchoolName ( name) {
console. log ( 'App 收到了学校名:', name)
}
}
School.vue
< button @click = "sendSchoolName" > 把学校名给App < / button>
props: [ 'getSchoolName'] ,
methods: {
sendSchoolName ( ) {
this . getSchoolName ( this . name)
}
}
方式2: 使用自定义函数,父组件App中定义自定义函数传递给子组件,子组件通过&emit触发自定义事件调用,父组件中定义函数接收传递过来的值
App.vue
< ! -- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v- on) -- >
< Student @atguigu = "getStudentName" / >
getStudentName ( name, . . . params) {
console. log ( 'App 收到了学生名:', name, params)
this . studentName = name
}
Student.vue
< button @click = "sendSchoolName" > 把学校名给App < / button>
methods: {
sendStudentlName ( ) {
this . $emit ( 'atguigu', this . name, 666 , 888 , 900 )
} ,
}
方式3: 使用ref标签,子组件通过&emit触发自定义事件调用,在父组件App中使用ref获取子组件标签同时搭配&on获取传递过来的值
App.vue
< ! -- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v- on) -- >
< Student @atguigu = "getStudentName" / >
methods: {
getStudentName ( name, . . . params) {
console. log ( 'App 收到了学生名:', name, params)
this . studentName = name
}
}
mounted ( ) {
this . $refs. student. $on ( 'atguigu', this . getStudentName)
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
< ! -- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v- on) -- >
< Student @atguigu = "getStudentName" / >
mounted ( ) {
this . $refs. student. $on ( 'atguigu', ( name, . . . params) = > {
console. log ( 'App 收到了学生名:', name, params)
console. log ( "this:" , this ) ;
this . studentName = name
} )
}
容易出错点1:
在父组件App中使用$on的箭头函数中把子组件传递过来的值赋值给父组件属性,会导致父组件属性接收不到信息
失败图片
成功图片
答案: 在父组件App中使用$on的普通函数this指代子组件的vc,而$on的箭头函数this指代父组件的vc,所以$on的普通函数的this.studentName = name就会赋值失败,正确写法就是上面说的,要么method定义赋值,$on中直接调用,要么$on中使用箭头函数去赋值
mounted ( ) {
this . $refs. student. $on ( 'atguigu', function ( name, . . . params) {
console. log ( 'App 收到了学生名:', name, params)
console. log ( "this:" , this ) ;
this . studentName = name
} )
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
mounted ( ) {
this . $refs. student. $on ( 'atguigu', ( name, . . . params) = > {
console. log ( 'App 收到了学生名:', name, params)
console. log ( "this:" , this ) ;
this . studentName = name
} )
}
容易出错点2:
问题:我在子组件Student 标签中使用了自定义事件,我想再绑定个原生事件,为啥原生事件失效了?
< Student ref= "student" @click = "show" / >
methods: {
show ( ) {
alert ( 123 )
}
}
答案: 因为原生事件也使用@注解,子组件标签默认会把@标识的原生事件理解为是自定义事件,所以就失效了,解决办法是在@click后面加.native标识符,这样子组件就知道这个click方法是原生方法。
< Student ref= "student" @click.native = "show" / >
再引出个思考点:为啥说子组件中根元素标签只能有一个,比如最外层只能有1个div标签,为啥能不能有2个?
答案: 给子组件标签绑定【自定义事件/原生事件】,实际是给里面的整个标签绑定函数,如果你有2个div,它哪知道给谁,如果2个都给岂不乱套了,当然这只是其中一个理由,实际子组件根元素标签只让有一个元素还有好多原因,只不过我还没学到。
3.14插槽分发
父子组件使用时,有时需要将父元素的模板跟子元素模板进行混合 ,这时就要用到slot插槽 进行内容分发, 简单理解就是在子模板定义中先占个位置等待父组件调用时进行模板插入 。
3.14.1slot插槽
注意: 在子组件模板定义中使用标签定义插槽位置,标签中可以填写内容,当父组件调用子组件且不传入内容时显示此标签体中内容,当父组件在引用子组件时,标签中的内容会放在子组件的插槽中。
举例
完整代码:
< div id= "app" >
< ! -- 传入数据 -- >
< child : msg= "msgText" >
< ! -- 传入模板, 混合子模板 -- >
< h4> 父组件模板< / h4>
< h5> 模板混入. . . . </ h5>
< / child>
< / div>
< template id= "child-template" >
< div>
< div> 我是子组件< / div>
< div> { { msg} } < / div>
< ! -- 定义slot插槽进行占位 -- >
< slot> 我是默认内容, 父组件不传入时我显示< / slot>
< / div>
< / template>
< script>
Vue . component ( 'child' , {
template: '#child- template',
props: [ 'msg' ]
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
msgText: '父组件数据'
}
} ) ;
< / script>
结果展示
3.14.2具名插槽
具名插槽slot, 就是给插槽起个名字 。在子组件定义时可以定义多个插槽,同时通过name属性指定一个名字就可实现匹配,如:,父组件引用时使用< slot=‘header’>进行匹配插槽插入元素。
注意点1: 子组件模板定义了两个插槽header和footer,分别使用name属性进行名称的指定,父组件引用子组件的标签中通过slot属性,来确定内容需要分发到哪个插槽里面。
大白话讲: slot标签通过配置name="header"名字,而父组件引用子组件的待传入标签通过配置slot="header"进行匹配插槽位置。
举例
完整代码:
< div id= "app" >
< ! -- 传入数据 -- >
< child : msg= "msgText" >
< ! -- 传入模板, 混合子模板 -- >
< h4 slot= "header" > 头部< / h4>
< h4 slot= "footer" > 底部< / h4>
< / child>
< / div>
< template id= "child-template" >
< div>
< ! -- 插槽header -- >
< slot name= "header" > < / slot>
< div> 我是子组件< / div>
< div> { { msg} } < / div>
< ! -- 插槽footer -- >
< slot name= "footer" > < / slot>
< / div>
< / template>
< script>
Vue . component ( 'child' , {
template: '#child- template',
props: [ 'msg' ]
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
msgText: '父组件数据'
}
} ) ;
< / script>
结果展示
3.14.3作用域插槽slot-scope
作用域插槽slot-scope,父组件通过插槽混入子组件的内容, 子组件也可以通过slot作用域向插槽slot内部传入数据,使用方式: ,父组件通过 进行引用。
大白话讲就是: 子组件插槽slot定义属性,让父组件待插入的标签内容可以直接使用slot定义的属性内容。
说明点1: 在slot标签中指定属性值,类似于props属性的使用,只不过是反过来的。即:组件标签中props属性用于父传子,而slot标签中指定属性值,是子传父。 说明点2: 引用时用template标签指定,slot-scope属性指定接收数据的变量名,就可以使用花括号形式取值了。
完整代码:
< div id= "app" >
< ! -- 传入数据 -- >
< child>
< ! -- slot- scope的值可以随便起变量名 -- >
< template slot- scope= "props" >
< div> { { msgText} } < / div>
< div> { { props. text} } < / div>
< / template>
< / child>
< / div>
< template id= "child-template" >
< div>
< ! -- 插槽text值 -- >
< slot text= "子组件数据" > < / slot>
< / div>
< / template>
< script>
Vue . component ( 'child' , {
template: '#child- template'
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
msgText: '父组件数据'
}
} ) ;
< / script>
3.14.4slot-scope版本更新
在2.5+之后,可以不局限于, 任何元素都可以,同时可以使用解构赋值的方式 进行数据解析。
子组件:
< template id= "child-template" >
< div>
< ! -- 插槽text值 -- >
< slot name= "head" text= "header" > < / slot>
< slot name= "foot" text= "footer" value= "18" > < / slot>
< slot name= "cont" text= "content" title= "main" > < / slot>
< / div>
< / template>
父组件使用:
< div id= "app" >
< ! -- 传入数据 -- >
< child>
< ! -- div标签使用slot- scope -- >
< div slot= "head" slot- scope= "props" > 子组件数据: { { props. text} } < span> { { fa} } < / span> < / div>
< div slot= "foot" slot- scope= "props" > { { props. text} } == { { props. value} } < / div>
< ! -- 结构赋值 -- >
< div slot= "cont" slot- scope= "{text, title}" > { { text} } == { { title} } < / div>
< / child>
< / div>
js部分:
Vue . component ( 'child' , {
template: '#child- template'
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
fa: 'father 数据'
}
} ) ;
结果展示
3.15.动态组件
使用标签的is属性,动态绑定多个组件到一个挂载点,通过改变is绑定值,切换组件。
3.15.1使用方式
举例:点击a标签下方切换不同组件
< div id= "app" >
/ < a href= '#' @click.prevent = "page='index'" > 首页< / a>
/ < a href= '#' @click.prevent = "page='news'" > 新闻< / a>
/ < a href= '#' @click.prevent = "page='login'" > 登陆< / a>
< hr>
< component : is= "page" > < / component>
< / div>
< script>
Vue . component ( 'index' , {
template: '< h5> 首页< / h5> '
} ) ;
Vue . component ( 'news' , {
template: '< h5> 新闻页< / h5> '
} ) ;
Vue . component ( 'login' , {
template: '< h5> 登陆页< / h5> '
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
page: 'index'
}
} ) ;
< / script>
结果展示
3.15.2keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令。 注意点1: 使用keep-alive标签嵌套component标签
注意点2: 用生命周期中的mounted(挂载)钩子函数进行组件渲染监听,当组件第一次被渲染后就保存在内存中,下次切换不会被重新渲染。
注意点3: 所谓的“避免重新渲染”指的是初始化后首次调用会渲染并打印输出钩子语句,等后续再次点击就不会触发打印,即达到了避免重新渲染的目的。
< div id= "app" >
/ < a href= '#' @click.prevent = "page='index'" > 首页< / a>
/ < a href= '#' @click.prevent = "page='news'" > 新闻< / a>
/ < a href= '#' @click.prevent = "page='login'" > 登陆< / a>
< hr>
< keep- alive>
< component : is= "page" > < / component>
< / keep- alive>
< / div>
Vue . component ( 'index' , {
template: '< h5> 首页< / h5> ',
mounted: function ( ) {
console. log ( '挂载. . . 首页') ;
}
} ) ;
Vue . component ( 'news' , {
template: '< h5> 新闻页< / h5> ',
mounted: function ( ) {
console. log ( '挂载. . . 新闻页') ;
}
} ) ;
Vue . component ( 'login' , {
template: '< h5> 登陆页< / h5> ',
mounted: function ( ) {
console. log ( '挂载. . . 登陆页') ;
}
} ) ;
var app = new Vue ( {
el: '#app' ,
data: {
page: 'index'
}
} ) ;
3.16 refs属性
使用ref 给每个组件起一个固定的名字,方便后续直接引用操作,在父组件中使用$refs访问子组件。
举例:使用自定义组件,默认打印count值为0,通过配置app.$refs.btn1.count = 1,父组件就可以达到获取ref标识的自定义组件并修改count值的目的。
< div id= "app" >
< child ref= "btn1" > < / child>
< / div>
< script>
Vue . component ( 'child' , {
template: '< button> { { count} } < / button> ',
data: function ( ) {
return { count: 0 }
}
} ) ;
var app = new Vue ( {
el: '#app'
} ) ;
app. $refs. btn1. count = 1 ;
< / script>
3.17.生命周期
3.17.1生命周期介绍
注意点1: 生命周期钩子函数中this指代vue对象,注意和监视属性下面图1中的this做对比,别混。 (即: 计算属性使用同步操作的普通函数,this => vue , 同步操作的箭头函数,this => window 监视属性的同步操作的普通函数,this => vue, 同步操作的箭头函数,this => window, 异步操作的普通函数,this => window, 异步操作的箭头函数,this => vue, 具体原因看下面图)。
3.17.2生命周期钩子
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。 比如 created 钩子可以用来在一个实例被创建之后执行代码; 常用的生命周期钩子函数有:
1 created: 实例创建完成后被立即调用
2)mounted: 实例挂载完成后被立即调用
3 beforeUpdate: 实例需要渲染之前调用
4)updated: 实例更新后调用
5)destroyed: Vue 实例销毁后调用
3.17.3生命周期整体流程图
注意点1:
问题:钩子函数beforeCreate和created代表创建前和创建后,这个创建指的是谁?
答案: 指的是“数据监测和数据代理”也就是data属性和属性的get()和set(),一定不是指代vue实例,千万别弄混。
注意点2: 图中的判断元素“Has template option?”中的template 指的是图1元素(也就是data中的template属性),而不是图2元素(图2中的template 是标签)
图1
图2
注意点3: 图3中的outerHtml指的是图4中绿色框内容,与其对立的interHtml指的才是红色框内容
图3
图4
注意点4:
问题:图5步骤会把之前生成内存中的虚拟DOM转成真实DOM,转成之后会往“vm.$el”上存了一份,问:为什么要保存一份?
答案: 可以用在很多地方,比如vue提供了虚拟DOM和真实DOM比较算法,既然有比较那么去哪里拿虚拟DOM呢?诶,“vm.$el”上面就保存了可以直接使用。
图5
注意点5:
问题:图6中红框如何理解?,为啥最终都不奏效,注意这个“最终”字眼
原因: 钩子函数beforeMount中无论做了什么操作都没用,因为执行到图7红框操作部分,只会把内存中虚拟DOM转为真实DOM插入页面中,会进行模板渲染覆盖,这样就会导致钩子函数beforeMount中无论做了什么操作都没用了。
图6
图7
注意点6: 在div中定义模板和在data中定义模板,页面长得不一样。 举例说明: div中定义模板如图8,页面效果如图9,页面会发现root容器还在,而如果data中定义模板如图10,页面效果如图11,页面会发现root容器消失了。总结大白话就是说,如图12使用data中定义模板方式,会造成绿色框会替换掉红色框内容,就会造成原来红色框定义的x属性等等都消失了,都白写了。
图8
图9
图10
图11
图12
注意点7: 面试官可能会问,在哪个钩子中页面和数据尚未保持同步啊?所谓数据未同步指的是data数据值已经改了,而页面却没进行动态更新。如图13 答案就是beforeUpdate钩子函数中
图13
注意点8: 官网红框容易造成歧义
图14
注意点9: 一般不会在钩子函数beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。大白话说就是哪怕钩子函数beforeDestroy中执行修改属性操作了,值也改变了,但是页面也不会触发更新操作了,即值改了 但页面不更新了。
注意点10: vue生命周期流程图中只展示了8个钩子,实际还有3个隐藏的钩子没显现出来,它们旨在特殊场合才会显示出来,即“实现路由切换”功能时才会讲诉这3个隐藏的钩子。
注意点11: 3个隐藏的钩子:nextTick、activated、deactivated 其中:nextTick请看3.24知识点,而activated、deactivated用于路由组件。
举例:代码说明
< div id= "root" >
{ { name} }
< / div>
< script>
var vm = new Vue ( {
el: '#root' ,
data: {
name: 'Tim'
} ,
created: function ( ) {
console. log ( '实例创建. . . ') ;
} ,
mounted: function ( ) {
console. log ( '实例挂载. . . ') ;
} ,
beforeUpdate: function ( ) {
console. log ( '实例将要更新. . . ') ;
} ,
updated: function ( ) {
console. log ( '实例已更新. . . ') ;
} ,
destroyed: function ( ) {
console. log ( '实例卸载. . . ') ;
}
} ) ;
vm. name = 'Cat' ;
< / script>
结果展示
3.18混入
注意点1: 所谓“混入”,就是把vue组件中共同的配置提取出来,单独用一个文件保存,比如叫mixin.js保存配置,使用时引入并配置即可使用。
注意点2:
问题:如何使用混入?
答案: 第一步引入,第二步配置mixins。
mixin.js
export const hunhe = {
methods: {
showName ( ) {
alert ( this . name)
}
} ,
mounted ( ) {
console. log ( '你好啊!' )
} ,
}
export const hunhe2 = {
data ( ) {
return {
x: 100 ,
y: 200
}
} ,
}
组件内使用时引入并配置
import { hunhe, hunhe2} from '. . /mixin'
export default {
name: 'School' ,
data ( ) {
return {
name: '尚硅谷' ,
address: '北京' ,
x: 666
}
} ,
mixins: [ hunhe, hunhe2] ,
}
注意点3: mixin.js中写法export const hunhe = {},这叫ES模块化的分别暴露。
注意点4: 组件中使用混入,需指定mixins:[],必须为数组才有效。
注意点5: 所有的vue的配置项都可以写在“混入”里面。
注意点6: 混入文件mixin.js中有一个属性名,而data中未定义相同的属性名,那么值以“混入”中定义的为主 ,如果mixin.js中和data中有相同属性名,那么以data中配置的数据或方法为主 ,说白了“混入”不破坏你的代码。
注意点7(特殊情况):
问题:和注意点6对比,比如“混入”中定义了钩子函数,而data中定义了相同名称的钩子函数,那么以谁为主?
答案: 钩子函数会特殊处理,vue不以任何人为主,vue它都要,另外注意加载顺序 ,“混入”中钩子函数先加载,组件中同名钩子函数后加载。
注意点8: 混入有局部混入,还有全局混入
“局部混入”方式,使用mixins:[]
组件中
< script>
import { hunhe, hunhe2} from '. . /mixin'
export default {
name: 'School' ,
data ( ) {
return {
. . .
}
} ,
mixins: [ hunhe, hunhe2] ,
}
“全局混入”方式,使用Vue.mixin(),注意弊端:Vue.mixin(hunhe)会导致所有的vm、vc都拥有“混入”配置
main.js中
import { hunhe, hunhe2} from '. /mixin'
Vue . mixin ( hunhe)
Vue . mixin ( hunhe2)
注意点9: 使用“混入”步骤还是3步:
1)定义,提取成mixin.js文件,放在跟A匹配。App.vue同级别下
2)注册,分局部)注册和全局)注册
3)main.js中引入
3.19插件
注意点1:
问题:vue插件是啥?
答案: vue插件是在实例化vm之前,使用插件能够动态新增一系列功能的东西,比如添加一堆全局过滤器、或者添加一堆全局指令…。(大白话总结:插件就是类似外挂,每次进游戏前会动态新增一系列暴力功能)
注意点2:
问题:如何使用插件?
答案: src目录下新建pluins.js文件,里面写类似下面这样的代码,在main.js中使用Vue.use(plugins)命令去添加插件
pluins.js文件
export default {
install ( Vue , x, y, z) {
console. log ( x, y, z)
Vue . filter ( 'mySlice', function ( value) {
return value. slice ( 0 , 4 )
} )
Vue . directive ( 'fbind' , {
bind ( element, binding) {
element. value = binding. value
} ,
inserted ( element, binding) {
element. focus ( )
} ,
update ( element, binding) {
element. value = binding. value
}
} )
Vue . mixin ( {
data ( ) {
return {
x: 100 ,
y: 200
}
} ,
} )
Vue . prototype. hello = ( ) = > { alert ( '你好啊' ) }
}
}
main.js文件
Vue . use ( plugins)
注意点3: 定义插件配置中函数,必须叫install(),不能改名
注意点4: install()接收的第一个参数叫Vue,这个Vue指的是缔造vm实例的Vue原型的构造函数
注意点5: 使用插件时是可以传递多个参数的,比如
main.js文件
Vue . use ( plugins, 1 , 2 , 3 )
pluins.js文件
export default {
install ( Vue , x, y, z) {
console. log ( x, y, z)
}
3.20 标签属性scoped用于控制样式
注意点1:
问题:我有两个子组件,拥有重复名称但样式不同的class,那么父组件在加载两个子组件时以谁的样式为准呢?
答案: 如图1以import的加载顺序为准,后加载会覆盖同名先加载的样式,而不以使用组件标签顺序为准,那么如何解决该问题呢?答案是在组件样式标签中配置scoped,比如如图2,它是怎么办到的呢,其实啊配置scoped后相当于动态给标签内的标签中加了个特殊属性,而且后面的值是每次运行随机生成的,通过”该特殊属性值“配合”控制标签属性选择器“一同控制样式,具体如图3
图1
图2
图3
注意点2: