组件
- 组件(Component )是Vue.js最强大的功能之一
- 特性 一个页面由不同的组件构成
- 组件 可以扩展HTML元素,封装可重用的代码
组件注册
全局注册
-
格式:Vue.component(“组件名称”, 导入的组件)第一个参数是组件名称(标签名),第二个参数要引入的组件
-
为什么要用短横线的方式使用组件,因为在html中对大小写不敏感(也就是不区分大小写)
-
全局组件注册后,任何的Vue实例(组件 )都可以用
<!-- App文件 --> <div id="app"> <!-- 4、组件可以重复使用多次 data是一个函数由于作用域的关系,每个组件的数据互不干扰 --> <!--使用组件两种写法。直接写文件名 DuttonCounter 或遇到大写加- button-counter(常用) --> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> <!-- 、必须使用短横线的方式使用组件 --> <!-- 组件的名称相当于HTML标签 --> <hello-world></hello-world> </div> <!-- main.js文件 --> <script > //@=src文件夹 import HelloWorld from '@/components/HelloWorld.vue' import DuttonCounter from '@/components/DuttonCounter.vue' //、如果使用驼峰式命名组件,必须使用短横线的方式使用组件 Vue.component('HelloWorld',HelloWorld ); Vue.component('DuttonCounter',DuttonCounter ) </script> -
全局注册的注意事项
- data必须为函数返回一个对象的结构 (data是函数时,在重复使用同一个组件时,由于每个组件都有自己的作用域,每个组件相互独立,互不响应)
- 为什么data不能为对象,因为data如果是对象也就是引用数据类型 ,在重复使用同一个组件时,每一个组件的data都是同一个地址,使用data数据会相互影响,也就是浅拷贝。
局部注册
-
局部组件只要将它引入其他组件就是子组件
-
三个步骤都要写不能会报错
<div id="app"> <!-- 使用组件 --> <my-component></my-component> </div> <script> //1:导入文件 import MyComponent from '@/component/MyComponent.vue' export default { mane:'app' //2:调用组件 components:{ MyComponent } } }) </script>
Vue组件之间传值
父组件向子组件传值
-
在父组件中使用子组件传值:在子组件身上定义属性并绑定要传递的值
-
在子组件中使用props接收
-
props是单身数据流,只能用在父传子
-
:title加
:与不加:的区别 ,如果不加引号title传递的值是字符串如果加引号
title是一个动态的变量用来传递数据
父组件传递
<div id="app">
<div>{{pmsg}}</div>
<!--1、menu-item 在 APP中嵌套着 故 menu-item 为 子组件 -->
<!-- 给子组件传入一个静态的值 -->
<menu-item title='来自父组件的值'></menu-item>
<!-- ptitle是父组件中data的数据
传的值可以是数字、对象、数组等等 -->
<!--使用-->
<menu-item :title='ptitle' content='hello'></menu-item>
</div>
<script>
//引入
import MenuItem from '@/components/MenuItem.vue'
export default {
name: 'App',
data:()=({
ptitle: 123
}),
components:{
//调用
MenuItem
}
}
</script>
子组件接收数据
<template>
<div>
<!-- 接收完数据,直接使用-->
<div >{{title}} </div>
<div>{{content}} </div>
</div>
</template>
<script>
export default {
//props 接收父组件数据,里面写父组件自定义的属性名可以是数组方式,也可以是对象方式
// 数组方式
//props:['title','content']
// }
//对象方式 可以写更多配置
props:{
title:{
type: Numder//type 用来约束数据类型
//多个数据类型写法type:[String, Numder]
//充许写的类型有 String Numder Boolean Array Object
dafault:1312//dafault默认值
},
content:{
type:string
required:true//该项数据必须传值
}
}
</script>
props属性
-
Props只读取
-
props中的数据不能直接修改
-
如果实在需要修改props中的数据,可以转存data中
//数组 props:['title','content'] //对象 props:{ title:{ type: Numder dafault:1312 }, content:{ type:string required:true }
父亲直接访问孩子:通过 ref 获取子组件
ref 有两个作用:
- 如果你把它作用到普通 HTML 标签上,则获取到的是 DOM
- 如果你把它作用到组件标签上,则获取到的是组件实例
在使用组件的时候,添加 ref 属性:
<blog-post title="My journey with Vue" ref="post"></blog-post>
//然后使用$refs.post
this.$refs.post;
使用建议:不在万不得以的情况下,不要通过这种方式修改数据,如果滥用这种方式会导致数据管理的混乱。
子组件向父组件传值
- 子组件用
$emit()触发事件,触发绑定在父组件的自定义事件(也就是$emit()的第一个参数) $emit()的第一个参数为自定义事件名称,第二个参数为需要传递的数据- 父组件等待子组件触发事件
- 子组件通过调用父组件的方法来完成数据的传递,本质就是调用函数传参
子组件传递
<template>
<div >
<button @click='btn'>按钮 </button>
<!-- $emit可以写到标签中,也可以写在methods-->
<button @click='$emit('button','123')'>按钮 </button>
</div>
</template>
<script>
export default {
methods:{
btn(){
//调用$emit方法,触发props事件
this.$emit('button','1323')
}
}
}
</script>
父接收
<template>
<!--父组件准备的事件名,必须和子组件触发事件一致 -->
<my-left @button="grtLeft"></my-left>
<!-- 可以使用在 methods内写处理函数-->
<my-left @button="val=>(username=val)"></my-left><!-- 也可以使用在标签内写处理函数方法 -->
</template>
<script>
export default{
data:()=>({
username:'12'
}),
methods:{
grtLeft(age){
//事件处理函数,通过参数接收传递的值
console.log(age)
}
}
}
</script>
V-model的底层实现
- 通过父传子,子传父来实现的
- 注意自定义属性和自定义事件,一定要写,value ,input ,因为v-model,就是通过这两个名来写的
- 父组件
<template>
<div>
<!-- 使用v-model代替:value和@input -->
<!-- <my-left v-model="age"> </my-left> -->
<my-left :value="age" @input="val=>(age=val)"> </my-left>
</div>
</template>
<script>
export default{
data()=>({
age:'12'
})
}
</script>
- 子组件
<template>
<div>
<input type="text" :vaule="vaule" @input="$emit('input',$event.target.value)"/>
</div>
</template>
<script>
export default{
props:['vaule']
}
</script>
- 改变子组件自定义
prop和event
<template>
<div>
<input type="text" :vaule="vau" @input="$emit('inp',$event.target.value)"/>
</div>
</template>
<script>
export default{
props:['vau']
},
//通过 model里面的prop改变接收的自定义属性
//通过even来改变事件名
model:{
prop:'vau',
even:'inp'
}
</script>
-
非父子组件通信
非父子组件通信:Event Bus
我们可以使用一个非常简单的 Event Bus 来解决这个问题:
event-bus.js:
//创建一个Vue实例对象
import Vue from 'vue'
export default new Vue();
数据接收调用bus.$on('事件名称','事件处理函数')方法注册一个自定义事件
import bus from '@/bus'
export default{
data:()=>({
username:''
}),
//自定义事件,
created(){
//share事件名称,处理函数
bus.$on('share',val=>(this.username=val))
}
}
发送数据调用bus.$emit('事件名',发送的数据)方法触发自定义事件
<template>
<button @click="sen"> </button>
</template>
<script>
import bus from '@/bus'
export default{
methods:{
sen(){
bus.$emit('share','1213')
}
}
}
</script>
兄弟之间的传递
-
兄弟之间传递数据需要借助事件中心,通过事件中心传递数据
- 提供事件中心 var hub=new Vue()
-
传递数据方,通过hub.$emit(方法名,传递的数据 )事件触发兄弟的钩子函数
-
接收数据方,通过mounted(){hub.$on(“方法名”, (接收传递过来的数据)=>{} }
-
销毁事件, 通过hub.$off() 方法名销毁事件之后无法进行传递数据
<div id="app"> <div>父组件</div> <div> <button @click='handle'>销毁事件</button> </div> <test-tom></test-tom> <test-jerry></test-jerry> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 兄弟组件之间数据传递 */ //1、 提供事件中心 var hub = new Vue(); Vue.component('test-tom', { data: function(){ return { num: 0 } }, template: ` <div> <div>TOM:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件 hub.$emit('jerry-event', 2); } }, mounted: function() { // 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on(方法名 hub.$on('tom-event', (val) => { this.num += val; }); } }); Vue.component('test-jerry', { data: function(){ return { num: 0 } }, template: ` <div> <div>JERRY:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件 hub.$emit('tom-event', 1); } }, mounted: function() { // 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名 hub.$on('jerry-event', (val) => { this.num += val; }); } }); var vm = new Vue({ el: '#app', data: { }, methods: { handle: function(){ //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据 hub.$off('tom-event'); hub.$off('jerry-event'); } } }); </script>
组件插槽
- 组件的最大特性就是利用性,而用好插槽能大大提高组件的复用性
- 父组件向子组件传递内容
匿名插槽
<div id="app">
<!-- 这里的所有组件标签中嵌套的内容会替换掉slot 如果不传值 则使用 slot 中的默认值 -->
<alert-box>有bug发生</alert-box>
<alert-box>有一个警告</alert-box>
<alert-box></alert-box>
</div>
<script type="text/javascript">
/*
组件插槽:父组件向子组件传递内容
*/
Vue.component('alert-box', {
template: `
<div>
<strong>ERROR:</strong>
# 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
# 插槽内可以包含任何模板代码,包括 HTML
<slot>默认内容</slot>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
具名插槽
-
如slot标签上有属性name,则它是叫具名插槽,否则叫匿名插槽
-
根据标签中的属性
slot='header',对应组件中的模板<slot name='header'></slot>进行匹配 -
使用
<template slot='header'> </template>标签包裹可以传递多条内容并且template不会渲染到页面上 <div id="app"> <base-layout> <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上 如果没有匹配到 则放到匿名的插槽中 --> <p slot='header'>标题信息</p> <p>主要内容1</p> <p>主要内容2</p> <p slot='footer'>底部信息信息</p> </base-layout> <base-layout> <!-- 注意点:template临时的包裹标签最终不会渲染到页面上 --> <template slot='header'> <p>标题信息1</p> <p>标题信息2</p> </template> <p>主要内容1</p> <p>主要内容2</p> <template slot='footer'> <p>底部信息信息1</p> <p>底部信息信息2</p> </template> </base-layout> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 具名插槽 */ Vue.component('base-layout', { template: ` <div> <header> ### 1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字 <slot name='header'></slot> </header> <main> <slot></slot> </main> <footer> ### 注意点: ### 具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序 <slot name='footer'></slot> </footer> </div> ` }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>
作用域插槽
-
父组件对子组件的内容加工
-
在父组件中使用
slot-scope属性,接收子组件绑定的属性上的数据 ,然后进行加工。<div id="app"> <!-- 1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件, 但样式希望不一样 这个时候我们需要使用作用域插 --> <fruit-list :list='list'> <!-- 2、 父组件中使用了<template>元素,而且包含slot-scope="slotProps", slotProps在这里只是临时变量 ---> <!-- 用slot-scope接收子组件item属性上的数据,然后用v-if加工数据--> <template slot-scope='slotProps'> <strong v-if='slotProps.info.id==3' class="current"> {{slotProps.info.name}} </strong> <span v-else>{{slotProps.info.name}}</span> </template> </fruit-list> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 作用域插槽 */ Vue.component('fruit-list', { //用props来接收父组件中的数据 props: ['list'], template: ` <div> <li :key='item.id' v-for='item in list'> ### 3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx", ### 插槽可以提供一个默认内容,如果父组件没有为这个插槽提供了内容,会显示默认的内容。 如果父组件为这个插槽提供了内容,则默认的内容会被替换掉 <slot :info='item'>{{item.name}}</slot> </li> </div> ` }); var vm = new Vue({ el: '#app', data: { list: [{ id: 1, name: 'apple' },{ id: 2, name: 'orange' },{ id: 3, name: 'banana' }] } }); </script> </body> </html>
组件样式
支持less和scss
- 写法
<!--默认样式 css 写法 ,如果要改变样式写法可以使用 lang来指定写法 -->
<style lang="less"></style>
<!--或者 -->
<style lang="scss"></style>
- 安装所需的包
# 使用less,需要安装下面的包
npm i less-loader@7.3.0 -D #(@vue/cli 4.5版本需要指定版本安装)
npm i less-loader #(@vue/cli 5.0.1,直接安装)
# 使用scss,需要安装下面的包
npm i sass-loader@10 sass -D #(@vue/cli 4.5版本需要指定版本安装)
npm i sass sass-loader -D #(@vue/cli 5.0.1,直接安装)
样式冲突解决
<!-- 直接给组件添加 scoped 就可以,这样就可以给单独的组件设置样式,解决冲突-->
<style lang="less" scoped></style>
scoped的原理
- 就是给单当前组件添加相同的自定义属性
- 在通过当前组件全部样式,都通过属性选择器选择
<!--给当前组件都添加,相同的自定义属性 -->
<template>
<div data-v-7ba5bd90>
<p> 123</p>
<div class="abc" data-v-7ba5bd90>这是一个div</div>
</div>
</template>
<!-- 当前组件全部样式,都通过属性选择器选择-->
<style lang="less">
p[data-v-7ba5bd90] {
color: red;
}
.abc[data-v-7ba5bd90] {
color: darkred;
}
</style>
样式穿透
- 用于父元组件改子组织的样式
//注意穿透不同样式写法也不同
<style scoped>
// css穿透 .abc父元素,p子元素
.abc >>> p{
color:red;
}
//less穿透
.abc/deep/p{
color:red;
}
.abc::v-deep p{
color:red;
}s
<style>
动态类名
- 动态类名
:class - 一个类名使用一个表达式就可以
- 多个类名使用,数组格式
- 使用复杂表达式控制类名
<template>
<div>
<!--利用三元运算符判断,点击button来更改flag值,继续切换类名 -->
<p :class="['a',flag ? 'thin':'']">1213</p>
<button @click="flag=!flag">却换类名 </button>
</div>
</template>
<script>
export default{
data:()=>({
flag=true
})
}
</script>
<style>
.a{
color:red
}
.thin{
color:yellow
}
</style>
ref引用
普通元素的ref
- Vue js中通过ref属性找到元素
- 每个vue的组件实例上,都包含一个
$refs对象,里面存储着对应的DOM元素或组件的引用 - 默认情况下,组件的$refs指向一个空对象
<template>
<div>
<!-- metRef名字自定义,结尾最好加ref -->
<input type="text" ref="metRef"/>
<button @click="fn">点击获取值</button>
</div>
</template>
<script>
export default{
methods:{
fn(){
console.log(this)//this里面有resf这个对象
//通过this.$resf.gaeRef.value找到当前元素的值
const val=this.$resf.gaeRef.value
}
}
}
</script>
组件的ref
- 使用组件的时候也可以使用ref属性
- 使用这个方法可以做很多事,比如获取子组件的数据和调用方法
<template>
<div>
<my-left ref='gaeRef'></my-left>
<button @click="fn">点击获取值</button>
</div>
</template>
<script>
export default{
methods:{
fn(){
//调用自组件的aeg数据项,前提是子组件必须有aeg这项数据
const val=this.$resf.gaeRef.aeg
//调用子组件的show方法
this.$resf.gaeRef.show()
}
}
}
</script>
$nextTick()
-
$nextTick()作用将回调延迟到下一次DOM更新之后执行
//在使用this.$refs.gaeRef,可能会出现找不到 //因为在调用这个方法后,代码马上就执行了,还没有渲染到页面中, //我们需要等待DOM更新,更新完毕,才执行这个代码 //所以可以用$nextTick(),将回调延迟到下次DOM更新之后执行 methods:{ getLeft(){ this.flag=true this.$nextTick(()=>{ this.$refs.gaeRef.show() }) } }
动态组件
初步使用
动态组件指的是动态切换组件的显示隐藏
- vue提供的一个内置组件,专门用来实现动态组件的渲染
<template>
<div>
<component :is="com"></component>
<div>
<button @click="com=Myleft"> </button>
<button @click="com=c"> </button>
</div>
</div>
</template>
<script>
import Myleft from '@/components/Myleft.vue'
import Myleft from '@/components/Mytop.vue'
export default{
data:()=>({
com:Myleft
}),
components:{
Myleft
Mytop
}
}
</script>
保持组件状态
默认情况下,切换动态组件时无法保持组件的状态
- 如有两个组件来回切换,其中一个改变了数据,但一切换组件又回到了初始状态
如果要保持组件状态可以使用vue内置的 组件保持动态组件的状态
- 通过
include属性指定那个组件需要被缓存(不指定就不会缓存)
<!--注意:如果需要缓存多个组件,多个组件用逗号分割,绝对不能加空格 -->
<keep-alive include="Mytop,Myleft">
<component :is="com"></component>
</keep-alive>
\