组件的注册
在 vue
中,我们可以通过 new Vue
来创建一个组件,不过通常它是作为整个应用的顶层根组件存在的,我们还可以通过另外的方式来注册一个更为通用的组件
组件
- 根组件:通过new Vue()来创建的,通常应用中只有一个
- 可复用性组件:通过Vue.component()来创建
分为全局组件,局部组件
component全局不带s
局部带s ,components
Vue.component()
Vue.component('组件名称', {组件选项})
- 组件名称遵循自定义组件命名规范:全小写、连字符(虽然驼峰式一般也没问题)
- 组件选项与
new Vue
选项配置基本一致(也有一些细节的不同)
全局组件与局部组件
通过 Vue.component
注册的组件,我们称为全局组件,因为它可以在任意范围内使用,我们还可以定义局部组件
Vue.component("d-div",{
template:`
<div>
<div>div1</div>
<p-p-t></p-p-t>
<div>div2</div>
</div>
`,
components:{
// 组件名称:组件选项
'p-p-t':{
template:`<div>我是一个div</div>`
}
}
})
在一个组件内部通过 components
选项注册的组件是局部组件,只能在当前 components
选项所在的组件内部使用
注意:局部注册的组件只能中当前注册的组件中使用,不能在它的子组件中使用
局部组件
let app = new Vue({
el:"#app",
// 局部注销可复用性组件的方式
component:{
}
});
data
在非 new Vue
的组件中,data
必须为函数,函数返回值必须是一个对象,作为组件的最终 data
<script>
Vue.component("d-div",{
template:`
<div>div1</div>
<div>div2</div>
`,
data(){
return{
name:"孩子"
}
}
})
let app = new Vue({
el:"#app",
data:{
par:"父亲"
}
})
</script>
组件的传参(父传子)props
组件中内部私有数据存储中组件 data
中,通过外部传入的数据,则通过 props
选项接收
- 如果传入的
props
值为一个表达式,则必须使用v-bind
- 组件中的
data
和props
数据都可以通过组件实例进行直接访问 data
中的key
与props
中的key
不能冲突
<div id="app">
<p v-text = "name"></p>
<!-- 父传 使用v-bind绑定属性的方式 -->
<d-div :page = "10"></d-div>
</div>
<script>
// 可复用性组件中:最上层只能有一个元素(根元素)
Vue.component("d-div",{
// 子接:使用属性props
// props:用来存储数据
// 组件内部使用数据的使用方式和data一致,直接通过this调用
props:["page"],
template:`
<div>
<div>{{name}}</div>
<div>div2 {{page}}</div>
</div>
`,
data(){
return {
name:"儿子"
}
}
})
let app = new Vue({
el:"#app",
data:{
name:"父亲"
}
})
</script>
组件通讯
-
父传子:
父级调用子组件,通过子组件的属性传入数据
子元素内部通过prop配置项(数组),来接受对应的数据 -
子传父:
注意
:Vue中的数据默认的单项流动,只能父到子直接传递, 但是子到父不能直接修改原因
:因为父级的数据,不一定只是某个子级使用,或许还有其他的子级也在使用,那么如果一个子级内部随意去修改了父级的数据,很容易导致数据混乱
组件的传参(子传父) $emit()
vue
为每个组件对象提供了一个内置方法 $emit
,它等同于自定义事件中的 new Event
,trigger
等
this.$emit('自定义事件名称', 事件数据)
-
事件数据就是中触发事件的同时携带传递的数据 -
event
-
父级在使用该组件的过程中,可以通过
@事件名称
来注册绑定事件函数 -
事件函数的第一个参数就是事件数据
如果子级想修改数据:
1.子级执行 $emit()来触发自定义事件
2.父级监听 子级触发的自定义事件
3.监听到触发 执行父级的回调函数
子级在特定条件下,触发自定义事件来通知父级,父级通过监听接收到这个通知后,自己决定是否改变数据或者说是如何改变
<div id="app">
<!-- 父级监听自定义事件 -->
<d-div @:d-button = "fn"></d-div>
<p>我是子元素传来的{{name}}</p>
</div>
<script>
// 子传父 传参
Vue.component("d-div",{
data(){
return{
name:"姓名"
}
},
template:`
<div>
<p>我是第一段</p>
<p>我是第二段</p>
<button @click = "go">按钮</button>
</div>
`,
// 传参
methods:{
// 函数
go(){
// if(){
// 参数1:自定义事件 (自定义事件名称不能使用 大写字母)
// 参数2:传递的参数
this.$emit("d-button",this.name)
// }
}
}
})
let app = new Vue({
el:"#app",
data:{
name:""
},
methods:{
fn(n){
console.log(n);
this.name = n;
}
}
})
</script>
组件双绑的实现
虽然并不推荐在组件内部修改 props
,但是,有的时候确实希望组件内部状态变化的时候改变 props
,我们可以通过子组件触发事件,父级监听事件来达到这个目的,不过过程会比较繁琐,vue
提供了一些操作来简化这个过程
v-model
v-model
是 vue
提供的一个用于实现数据双向绑定的指令,用来简化 props 到 data
,data 到 props
的操作流程
通过v-model双项绑定数据
-
父传子:
1.子组件绑定父组件属性
2.子组件通过props接收使用
3.model中prop指定属性 -
子传父:
1.子组件触发自定义事件
2.model中指定自定义事件
3.自动监听,自动回调函数,自动赋值
v-model:不推荐使用v-model来传参,因为它隐藏了太多细节
- 1.会让我们使用的时候,操作空间变小
- 2.出现错误的时候,不容易排查
- 3.绑定不了多个prop
推荐使用:.sync修饰符
<div id="app">
<!-- 通过v-bind单项绑定数据 -->
<d-div :mm="msg" @g = "fn"></d-div>
<p>{{msg2}}</p>
<hr>
<!-- 通过v-model双项绑定数据 -->
<mc-m v-model = "rootmsg"></mc-m>
<p>我是父组件的:{{rootmsg}}</p>
</div>
<script>
Vue.component("mc-m",{
props:["mm","rootmsg"],
// model选项:就是用来指定绑定的属性和绑定的事件
model:{
// prop用来告诉v-model绑定的prop是那个
prop:'rootmsg',
// event:告诉v-model触发什么事件的时候,自动去修改绑定的值
event:'gofather' //封装了监听和回调,以及赋值
},
data(){
return {
name:"子级信息"
}
},
template:`
<div>{{mm}}
<button @click="go">按钮</button>
<p>我是子组件的:{{rootmsg}}</p>
</div>
`,
methods:{
go(){
this.$emit("gofather",this.name)
}
}
})
let app = new Vue({
el:"#app",
data:{
msg:"父级信息",
msg2:"",
rootmsg:"父级2"
},
methods:{
fn(n){
this.msg2 = n;
}
}
})
</script>
.sync
通过 v-model
来进行双向绑定,会给状态维护带来一定的问题,因为修改比较隐蔽,同时只能处理一个 prop
的绑定,我们还可以通过另外一种方式来达到这个目的
<div id="app">
<p>val1: {{val1}}</p>
<p>val2: {{val2}}</p>
<hr>
<kkb-radio :checked.sync="val1" :disabled.sync="val2"></kkb-radio>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const kkbRadio = {
props: ['checked', 'disabled'],
data() {
return {
status: this.checked,
dis: this.disabled
}
},
template: `
<div class="kkb-radio" :class="{'checked': status, 'disabled': dis}" @click="changeDis" @mouseover="setChecked" @mouseout="removeChecked"></div>
`,
methods: {
setChecked() {
this.status = true;
this.$emit('update:checked', this.status);
},
removeChecked() {
this.status = false;
this.$emit('update:checked', this.status);
},
changeDis() {
this.dis = !this.dis;
this.$emit('update:disabled', this.dis);
}
}
};
let vm = new Vue({
el: '#app',
data: {
val1: false,
val2: false
},
components: {
'kkb-radio': kkbRadio
},
methods: {
}
});
</script>
插槽
默认情况下,组件模板解析后会替换整个组件内容,如果我们想在组件引用被包含的内容,可以通过 vue
提供的内置组件 slot
来获取
<div id="app">
<kkb-dialog title="标题">
<p>这是内容</p>
</kkb-dialog>
</div>
const Dialog = {
props: ['title'],
template: `
<div class="dialog">
<div class="dialog_header">
<span class="dialog_title">{{title}}</span>
</div>
<div class="dialog_content">
//插槽写入
<slot></slot>
</div>
</div>
`
};
具名插槽
<div id="app">
<kkb-dialog>
<h1 slot="title">这是标题</h1>
<p slot="default">这是内容</p>
</kkb-dialog>
</div>
const Dialog = {
props: ['title'],
template: `
<div class="dialog">
<i class="dialog_close_btn"></i>
<div class="dialog_header">
<slot name="title"></slot>
</div>
<div class="dialog_content">
<slot></slot>
</div>
</div>
`
};
v-slot
使用内置组件 template
与 v-slot
指令进行配置,用来命名插槽,在组件模板中,通过 <slot name="插槽名字">
来使用
作用域插槽
组件内部与组件包含的内容属于不同的作用域(被包含的内容是组件父级下的作用域)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
a{
color: black;
text-decoration: none;
border: 1px solid yellowgreen;
padding: 10px;
margin: 10px;
}
.act{
background: #118888;
color: white;
}
</style>
<body>
<div id="app">
<ul>
<li v-for = "user of showUsers ">{{user.name}}</li>
</ul>
<!-- 父级监听子级函数 -->
<d-div :pages="uPages" :page="uPage" @changpage = "changpage">
<template v-slot:header="pro">
<span>当前页数{{pro.page}}</span>
</template>
<template v-slot:footer="pro">
<span>一共{{pro.pages}}页</span>
</template>
<!-- <span slot ="default">我是默认的</span> -->
</d-div>
</div>
<script>
Vue.component("d-div",{
props:["pages","page"],
template:`
<div>
<slot name="header" :page="page"></slot>
<a href="JavaScript:;" @click="prev">上一页</a>
<a href="JavaScript:;"
v-for="p of pages"
@click="gotopage(p)"
:class="{act:page===p}"
>{{p}}</a>
<slot name="default"></slot>
<a href="JavaScript:;" @click="next">下一页</a>
<slot name="footer" :pages="pages"></slot>
</div>
`,
methods:{
gotopage(p){
this.$emit("changpage",p)
},
prev(){
if(this.page-1>0){
// 子传父 参数1:自定义事件 参数2:传递的参数
this.$emit("changpage",this.page-1);
}
},
next(){
if(this.page+1>this.page){
this.$emit("changpage",this.page+1);
}
}
}
})
let app = new Vue({
el:"#app",
data:{
users:[
{id:1,name:"张三"},
{id:2,name:"李四"},
{id:3,name:"王五"},
{id:4,name:"小明"},
{id:5,name:"小红"},
{id:6,name:"小哦"},
{id:7,name:"小屏"},
],
uPage:1,
pagePage:2,
},
computed:{
// 总页数 = 数据的个数 / 每页的数量
//
uPages(){
return Math.ceil(this.users.length / this.pagePage);
},
showUsers(){
//数组截取的第一个参数
let start = (this.uPage - 1) * this.pagePage;
return this.users.slice(start,start+this.pagePage);
}
},
// 父组件接收子组件传递过来的参数
methods:{
changpage(pa){
console.log("我是子组件传过来的"+pa)
this.uPage = pa;
}
}
})
</script>
</body>
</html>