Vue组件
可以理解为将多个小的东西封装成一个大的东西,然后可以直接对大的东西来进行操作,组件的核心是VirtualDOM
(虚拟DOM)
组件在注册也分为全局组件与局部组件,无论是全局组件还是局部组件,变像来说它们也是一个Vue对象,所以它们内部具备与Vue一样的东西
全局组件
<body>
<div id="app">
<m-card></m-card>
<hr>
<m-card></m-card>
</div>
<template id="temp1">
<div class="box">
<div class="m-header">这是卡片头部</div>
<div class="m-body">
这是卡片身体
<button type="button" @click="abc">按钮</button>
<hr>
<ul>
<li v-for="(item,index) in userList" :key="index">{
{
item}}</li>
</ul>
</div>
<div class="m-footer">这是卡片底部</div>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
//注册全局组件的方法
Vue.component("m-card",{
template:"#temp1",
data(){
return {
userList:["张三","李四","王五"]
}
},
methods:{
abc(){
alert("你好啊");
}
}
});
new Vue({
el:"#app",
data:{
}
});
</script>
在上面的代码当中,我们就已经定义了全局组件m-card
,它通过Vue.component
方法来完成的,这个方法接收两个参数,第一个参数是数组虚拟化的名称m-card
,第二参数是一个对象(这个对象可以理成另一个小型的vue对象,它与之前我们的new Vue的配置大体相同)
在使用的时候我们直接通过组件的名称<m-card></m-card>
来使用就行了
注意:组件在定义名称的时候,如果是使用了驼锋命令,则在调用VirtualDOM
,一定要删除-
的形式来连接,同时template
标签下面只能有一个根标签
<div id="app">
<user-info-list></user-info-list>
</div>
<script>
//注册全局组件的方法
Vue.component("userInfoList",{
template:"#temp1",
//.........
});
</script>
局部组件
局部组件其实就是一个对象,在使用之前一定在components
属性里面注册一下
<script>
// 定义一个组件,如果通过Vue.component注册就是全局
let stuInfo = {
template:"#temp2"
}
let userInfo = {
template:"#temp1",
data(){
return {
msg:"hello,这是一个组件"}
},
components:{
//注册了局部组件
stuInfo:stuInfo
}
};
new Vue({
el:"#app",
data:{
},
components:{
abc:userInfo,
//注册了局部组件
stuInfo:stuInfo
}
})
</script>
全局组件只要是注意成功以后可以直接使用,而局组件是需要当前引用 组件里面的components
属性上面去进行配置
<body>
<div id="app">
<!-- 我要在这里使用stuInfo,又怎么办 -->
<stu-info></stu-info>
<hr>
<abc></abc>
</div>
<template id="temp1">
<div style="border: 1px solid black;">
<h2>{
{
msg}}</h2>
<!-- 我要在这里调用第2个组件,怎么办 -->
<stu-info></stu-info>
</div>
</template>
<!-- 又定了一个组件 -->
<template id="temp2">
<div style="border: 2px solid red;">
<button type="button">我是第二个组件</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
//全局组件
Vue.component("stu-info",{
template:"#temp2"
});
//局部组件
let userInfo = {
template:"#temp1",
data(){
return {
msg:"hello,这是一个组件"}
}
};
new Vue({
el:"#app",
data:{
},
components:{
abc:userInfo
}
})
</script>
在上面的代码当中,我们的stu-info
它是一个全局组件,所以在任何地方都可以直接使用,而userInfo
则只是一个局部组件,在使用的时候需要在当前的内部的components
属性上面进行设置
组件中的数据
组件既然可以理解成是一个小型的Vue对象,那么它的一些东西就跟Vue对象非常像,它内部也有数据data
,也有方法methods
等,唯独有两个不一样
- Vue使用
el
去接管一个区域,而组件是通过template
- Vue使用数据的时候是通过
data
对象,而组件里面则是data()
方法返回一个对象
new Vue({
el:"#app",
data:{
//这里是Vue的数据
}
})
//如果是组件则应该这么写
Vue.component("userInfoList",{
template:"#temp1",
data(){
return {
//组件的数据就写在这里
}
}
})
但是我要说的不只这一点,组件中的数据不仅仅只来源于组件内部data()
函数的返回,它还可以从父级对象获取数据
组件如果要接收外部传过来的数据,需要通过自定义属性
<body>
<div id="app">
<h2>我在父级渲染:{
{
userName}}</h2>
<movie-item :aaa="userName"></movie-item>
</div>
<!-- template下面只允许有一个根标签 -->
<template id="temp1">
<div style="border: 1px solid black;">
<h2>{
{
aaa}}</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("movie-item",{
template:"#temp1",
props:{
aaa:{
type:String,
default:"你好"
}
}
})
new Vue({
el:"#app",
data:{
userName:"标哥哥"
}
});
</script>
上面的核心代码就两行<movie-item :aaa="userName"></movie-item>
这个是传值
props:{
aaa:{
type:String,
default:"你好"
}
}
上面的代码就是接值,其中aaa
代表要接收值的名称,type
接收值的类型,default
代表如果没有传值的默认值是什么
上面的写法还可以简写成props:["aaa"]
即可
数据流的单向性
我们上个章节提到,组件内部的数据可以是自身的,也可以是从外部来获取的,但有一个特点,外部组件传递给内部的数据是不能够回传出去的,也不能更改
数据的传递是具有单向性的,它不能逆向改变,它只能顺向改变
<body>
<div id="app">
<h2>{
{
userName}}</h2>
<hr>
<aaa :user-name="userName"></aaa>
</div>
<template id="temp1">
<div style="border: 1px solid black;">
我是组件内部的:{
{
userName}}
<button type="button" @click="changeName">改变值</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var aaa = {
template:"#temp1",
props:["userName"],
methods:{
changeName(){
this.userName = "小姐姐";
}
}
}
new Vue({
el:"#app",
data:{
userName:"标哥哥"
},
components:{
aaa:aaa
}
})
</script>
在上面的代码当中,我们已经在外部传递了一个变量值给组件aaa的内部进行渲染,然后我们尝试着在组件内部去更改userName
的值的时候,控制台报错了,因为这违背了单向数据流的原则
现在的问题就是子级组件能够否有办法改变父级组件的值
破坏单向数据流
- 使用对象的浅拷贝来完成
- 在子组件的内部通知外部去改变
<body>
<div id="app">
<h1>{
{
userName}}</h1>
<hr>
<user-info :abc="userName"></user-info>
</div>
<template id="temp1">
<div style="border: 1px solid red;">
<h2>{
{
abc}}</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("user-info",{
template:"#temp1",
props:["abc"]
})
new Vue({
el:"#app",
data:{
userName:"标哥哥"
}
})
</script>
如果我们直接去改变子组件的abc
的时候,这个时候父级组件是会报错的,因为你的abc
是从父级组件下面的userName
得到的
使用面向对象的特性浅拷贝去完成
<body>
<div id="app">
<h1>{
{
obj.userName}}</h1>
<hr>
<user-info :abc="obj"></user-info>
</div>
<template id="temp1">
<div style="border: 1px solid red;">
<h2>{
{
abc.userName}}</h2>
<button type="button" @click="abc.userName='张珊'">改变传过来的值</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("user-info",{
template:"#temp1",
props:["abc"]
})
new Vue({
el:"#app",
data:{
obj:{
userName:"标哥哥"
}
}
})
</script>
这个时候我们传递的是对象,而对比也是可以通过这种种方式去改变父级组件的值,因为对象默认是浅拷贝
通过自定义事件完成
<body>
<div id="app">
<h1>{
{
userName}}</h1>
<hr>
<user-info :abc="userName" @bgg="changeName"></user-info>
</div>
<template id="temp1">
<div style="border: 1px solid red;">
<h2>{
{
abc}}</h2>
<button type="button" @click="ccc">改变传过来的值</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("user-info",{
template:"#temp1",
props:["abc"],
methods:{
ccc(){
//我要通知父级去修改这个值
// 触发一个自定义事件bgg
this.$emit("bgg","旺仔");
}
}
})
new Vue({
el:"#app",
data:{
userName:"标哥哥"
},
methods:{
changeName(str){
this.userName = str;
}
}
})
</script>
我们在子级组件的内部使用了this.$emit("bgg","旺仔")
这个方法,在这个方法的内部它会触发外部的一个自定义事件@bgg
,然后这个事件又调用了changeName
这个方法,这样就在外部组件改变了这个值,外部的值改变以后,内部的值也会发生改变
插槽slot
slot最要的目的就是在组件封装的时候,可以封装大多数相同的东西,而对于那些不太确定的东西可以给一个slot来占位。后期直接代替它
<body>
<div id="app">
<m-header>
123123
</m-header>
</div>
<template id="temp1">
<div style="border: 1px solid black;">
<slot></slot>
<h2>这是一个组件</h2>
<slot></slot>
</div>
</template>
</body>
<script src="./js/vue.min.js"></script>
<script>
Vue.component("m-header",{
template:"#temp1"
});
new Vue({
el:"#app"
});
</script>
上面的123
就会被插入到<slot>
的地方
<slot></slot>
不仅可以插一个,还可以给多个
<div id="app">
<m-header>
<button type="button" slot="leftBack">left的按钮</button>
<a href="#" slot="rightMenu">百度一下</a>
</m-header>
</div>
<template id="temp1">
<div style="border: 1px solid black;">
<slot name="leftBack"></slot>
<h2>这是一个组件</h2>
<slot name="rightMenu"></slot>
<hr>
<slot name="leftBack"></slot>
</div>
</template>
<slot>
可以根据name
值来进行区分,这种情况在新的版本里面已被废弃了,我们可以通过v-slot
来完成,具体看后面的具名插曹
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的在父级作用域当中是拿不到子级作用域的数据的
后备内容
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 <submit-button>
组件中:
<button type="submit">
<slot></slot>
</button>
我们可能希望这个 <button>
内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在 <slot>
标签内:
<button type="submit">
<slot>Submit</slot>
</button>
具名插槽
有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout>
组件:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
对于这样的情况,<slot>
元素有一个特殊的 attribute:name
。这个 attribute 可以用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带 name
的 <slot>
出口会带有隐含的名字“default”。【上面的代码是以前的写法】
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的
<body>
<div id="app">
<user-info>
<template v-slot:footer="scope">
<p>这是要插入在后面的内容----{
{
scope.username}}</p>
</template>
</user-info>
</div>
<template id="temp1">
<div style="border: 1px solid black;">
<h2>这是子级组件里面的数据----{
{
userName}}</h2>
<hr>
<slot name="footer" :username="userName"></slot>
</div>
</template>
</body>
<script src="./js/vue.min.js"></script>
<script>
Vue.component("user-info",{
template:"#temp1",
data(){
return {
userName:"张三丰"
}
}
})
new Vue({
el:"#app",
data:{
}
})
</script>
在上面的代码当中,我们通过<slot name="footer" :username="userName"></slot>
外边的插槽去传递了一个值username
,然后外边在进行插槽操作的时候可以直接通过v-slot:footer="scope"
来获取到
<template v-slot:footer="scope">
<p>这是要插入在后面的内容----{
{
scope.username}}</p>
</template>