Vue学习二——组件初始化、父子组件传值、插槽
一、组件化初始化
1.什么是组件以及组件化?
如果我们将一个页面中所有的处理逻辑全部放到一起。处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。为此,引出组件化的思想:
- 将一个完整的页面分成很多组件
- 每一个组件都用于实现页面的一个功能块
- 而每一个组件又可以进行细分
组件化提供了一种抽象,让我们可以开发一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一棵组件树。
2.注册组件的基本步骤
1.创建组件构造器
2.注册组件
3.使用组件
注册组件的步骤视图:
<div id="app">
<!-- 使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../JS/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template : `
<div>
<h2>我是标题</h2>
<p>hahaha</p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn',cpnC)
let app = new Vue({
el : '#app',
data : {
message : 'hello'
}
})
</script>
页面显示结果:
我是标题
hahaha
我是标题
hahaha
注册组件步骤解析:
-
Vue.extend():
调用Vue.extend()创建的是一个组件构造器 通常在创建组件构造器时,传入template代表我们自定义组件的模板 该模板就是在使用到组件的地方,要显示的HTML代码 事实上,这种写法在Vue2.x的文档中几乎以及看不到了,它会直接使用语法糖,是基础
-
Vue.component():
调用Vue.component()是将刚才的组件构造器注册成一个组件,并且给他起一个组件的标签名称 所以需要传递两个参数: 1.注册组件的标签名; 2.组件构造器
-
组件必须挂载在某一个Vue实例下,否则它不会生效
组件分类
-
全局组件和局部组件
全局注册组件的写法: Vue.component('cpn',cpnC) //这种写法在任何一个vue实例中均可以使用 局部注册组件的写法: //只可以在所挂载的某个实例中使用
let app = new Vue({
el : '#app',
data : {
message : 'hello'
},
components : {
// cpn就是使用组件时的标签名
// 局部注册组件,仅在app中可以使用
cpn : cpnC
}
})
- 父组件和子组件
在创建某一个组件中注册了另一个组件,其中创建组件的称为“父组件”,注册的组件称为“子组件”。
<div id="app">
<cpn2></cpn2>
</div>
<script src="../JS/vue.js"></script>
<script>
// 1.创建第一个组件构造器对象(子组件)
const cpnC1 = Vue.extend({
template : `
<div>
<h2>我是标题</h2>
<p>hahaha</p>
</div>`
})
// 创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template : `
<div>
<h2>我是标题</h2>
<p>heiheiheihei</p>
<cpn1></cpn1>
</div>
`,
//在组件二中注册组件一
components : {
cpn1 : cpnC1
}
})
// 2.注册组件
// Vue.component('my-cpn',cpnC1)
// root组件
let app = new Vue({
el : '#app',
data : {
message : 'hello'
},
components : {
cpn2 : cpnC2
}
})
</script>
在父组件中注册的子组件其作用域就是父组件,要是想要在全局作用还需要在vue实例中注册。
注册组件的语法糖:
- 全局组件语法糖
<script>
Vue.component('cpn1',{
template : `
<div>
<h2>我是全局组件语法糖</h2>
<p>hahaha</p>
</div>`
})
</script>
- 局部组件语法糖
<script>
let app = new Vue({
el : '#app',
data : {
message : 'hello'
},
components : {
'cpn2' : {
template : `
<div>
<h2>我是局部组件语法糖</h2>
<p>hahaha</p>
</div>`
}
}
})
</script>
组件模板的分离写法:
使用<script>标签:<script type="text/x-template" id="cpn">
<div id="app">
<!-- 使用组件 -->
<cpn></cpn>
</div>
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>hahaha</p>
</div>
</script>
<script src="../JS/vue.js"></script>
<script>
Vue.component('cpn',{
template : '#cpn'
})
let app = new Vue({
el : '#app',
})
</script>
使用<template>标签
<div id="app">
<!-- 使用组件 -->
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>hahaha</p>
</div>
</template>
<script src="../JS/vue.js"></script>
<script>
Vue.component('cpn',{
template : '#cpn'
})
let app = new Vue({
el : '#app',
})
</script>
注意:
组件可以访问Vue实例数据吗?
1.组件是一个单独功能模块的封装:
这个模块有属于自己的HTML模板,也应该有属于自己的数据data
2.组件不能直接访问Vue实例中的数据
Vue组件应该有自己保存数据的地方
组件数据的存放
1.组件对象也有一个data属性(也可以有methods等属性)
2.这个data属性必须是一个函数
3.而且这个函数返回一个对象,对象内部保存着数据
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>hahaha</p>
<button @click="btnClick">点击一下</button>
</div>
</template>
<script src="../JS/vue.js"></script>
<script>
Vue.component('cpn',{
template : '#cpn',
data() {
return {
title : 'hi'
}
},
methods : {
btnClick() {
console.log("click btn");
}
}
})
let app = new Vue({
el : '#app',
})
</script>
二、父子组件的通信——传值
1.缘由
- 子组件不能引用父组件或者Vue实例的数据
- 在开发中一些数据需要从上层传递到下层:
比如从服务器请求到很多数据,其中一部分数据需要由下面的子组件进行展示,这时并不会让子组件再次发送一个网络请求,而是直接让父组件将数据传递给子组件。- 父子组件间的通信方法
1.通过props向子组件传递数据;
2.通过事件向父组件发送信息
2.过程
(1) props基本用法——父传子
方式一:字符串数组,数组中的字符串就是传递时的名称
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等
<div id="app">
<!-- 使用v-bind绑定属性 -->
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<p>{{cmovies}}</p>
<ol>
<li v-for="item in cmovies">{{item}}</li>
</ol>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../JS/vue.js"></script>
<script>
const cpn = {
template : '#cpn',
props : ['cmovies','cmessage'], //采用数组,cmovies,cmessage是一个变量
data() {
return {}
},
methods : {}
}
const app = new Vue({
el : '#app',
data : {
message : 'hello',
movies : ['海的女儿','蜡笔小新','海绵宝宝']
},
components : {
cpn
}
})
</script>
补充:props数据验证
当需要对props进行类型等验证时,就需要对象写法了。
支持以下类型:
-String - Number - Boolean - Array
- Date - Function - Symbol - Object
<script>
const cpn = {
template : '#cpn',
props : {
//1.类型限制
cmovies : Array,
cmessage : String,
// 2.提供一些默认值,type为类型,default为默认值
ctitle : {
type : String,
default : 'Title',
required : true //必须使用v-bind绑定出现的属性
},
// 类型是对象或者数组时,默认值必须是一个函数
cmovies : {
type : Array,
default() {
return ['猫和老鼠']
}
}
},
data() {
return {}
},
methods : {}
}
</script>
Prop 的大小写 (camelCase vs kebab-case)
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
(2)自定义事件——子传父
自定义事件的流程:
1.在子组件中,通过$emit()来触发事件
2.在父组件中,通过v-on来监听事件
<body>
<!-- 父组件模板-->
<div id="app">
<cpn @item-click="cpnclick"></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<button v-for="item in list" @click="handleClick(item)">{{item.name}}</button>
</div>
</template>
<script src="../JS/vue.js"></script>
<script>
// 子组件
const cpn = {
template : '#cpn',
data() {
return {
list : [
{id : 'aaa', name : '热门推荐'},
{id : 'bbb', name : '手机数码'},
{id : 'ccc', name : '家用电器'},
{id : 'ddd', name : '电脑办公'}
]
}
},
methods : {
handleClick(item) {
// console.log(item.id);
// 发射事件:自定义事件
this.$emit('item-click',item)
}
}
}
let app = new Vue({
el : '#app',
data : {
message : 'hello'
},
components :{
cpn
},
methods : {
cpnclick(item) {
console.log("click cpn");
console.log(item.name);
}
}
})
</script>
</body>
(3)父子组件的访问
1.父组件访问子组件:使用$children或者 $refs
2.子组件访问父组件:使用 $parent (访问根组件使用 $root)
三、插槽——slot
- 组件的插槽
组件的插槽的目的:为了让我们封装的组件更加具有扩展性
让使用者可以决定组件内部的一些内容到底展示什么
例子:移动网站中的导航栏
几乎每一个页面都有导航栏,所以导航栏必然需要封装成一个插件,比如nav-bar组件。
一旦有了组件,就可以在多个页面中复用了。
- 如何封装这类组件?
抽取共性,保留不同
将共性抽到组件中,将不同的暴露为插槽,一旦预留了插槽,就可以让使用者根据自己的需求决定插槽中插入什么内容。
-
插槽的基本使用
(1)在子组件中写上
<slot></slot>
(2)插槽内部可以有默认值,例子:<slot><button>按钮</button></slot>
(3)如果有多个值同时放入到组件进行替换时,可以将其一起作为替换元素
该插槽插入什么内容取决于父组件如何使用
<slot>
中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
<div id="app">
<cpn></cpn>
<cpn><h4>hi</h4></cpn>
<cpn>
<p>我自己独有的p标签</p>
<h2>是我呀</h2>
</cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>哈哈哈</p>
<!-- 插槽就是预留一些空间,
具体使用的时候再根据实际写入 -->
<slot><button>按钮</button></slot>
<!-- <slot></slot> -->
</div>
</template>
-
具名插槽
给插槽一个name属性,选择需要更改的插槽中的内容,采用<xxx slot="name">
<div id="app">
<p>替换没有给名字的slot</p>
<cpn><span>标题</span></cpn>
<p>替换指定名字的slot</p>
<cpn><span slot="center">标题</span></cpn>
</div>
<template id="cpn">
<div>
<!-- 当给slot添加上名字之后父组件中的内容
不会被显示,没有给予单一名字的slot标签中的内容会被覆盖 -->
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
<br>
<slot><span>hahahha</span></slot>
</div>
</template>
- 编译作用域
父组件模板的所有东西都会在父级作用域内编译;
子组件模板的所有东西都会在子级作用域内编译
例子:
div id="app">
<!-- 此时v-show的作用域就是这个实例,isShow=true -->
<cpn v-show="isShow"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容</p>
<!-- 此时v-show的作用域是这个组件,
v-show=false,这个button不显示 -->
<button v-show="isShow"></button>
</div>
</template>
<script>
const app = new Vue({
el : '#app',
data : {
message : 'hi',
isShow : true
},
methods : {
},
components : {
cpn : {
template : '#cpn',
data() {
return {
isShow : false
}
},
}
}
})
</script>
- 作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供
在多个页面展示一组数据,但是展示效果不一样时,就需要父组件告诉我们如何展示,并且使用slot作用域插槽。
<div id="app">
<cpn></cpn>
<cpn>
<!-- 目的获取子组件中的planguage -->
<!-- 拿到slot对象:slot-scope="slot" -->
<div slot-scope="slot">
<span v-for="item in slot.data">{{item}} - </span>
<br>
<span>{{slot.data.join(' _ ')}}</span>
</div>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 动态绑定属性,将pLanguages的数据传给data -->
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
完成这一部分的学习,Vue不断更新,本博客一些内容可能有些滞后,博主正在努力学习中!