Vue2组件化开发
一、组件化开发的基本使用
①组件注册的基本步骤:
- 创建组件构造器
vue.exetend()
- 注册组件
Vue.componen()
- 使用组件
<div id="app">
<!-- 3.使用组件 -->
<!-- 组件必须放到vue的实例里才有效 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: ` <div>
<h2>我是一个组件</h2>
</div>`
})
// 2.注册组件
// 两个参数 一个是注册组件标签名 另一个是组件构造器
Vue.component('my-cpn',cpnC)
const app = new Vue({
el: '#app',
data: {
massage:'你好啊'
}
})
</script>
②全局组件和局部组件:
全局组件:意味着可以在多个vue实例下面使用
<!-- 全局组件的使用 -->
<div id="app">
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
<script src="../vue.js"></script>
<script>
const cpnC = Vue.extend({
template: ` <div>
<h2>我是一个组件</h2>
</div>`
})
// 注册全局组件,意味着可以在多个vue实例下面使用
Vue.component('cpn',cpnC)
const app = new Vue({
el: '#app',
})
const app2 = new Vue({
el: '#app2',
})
</script>
局部组件: 在一个vue实例里注册的组件是局部组件
<!-- 全局组件的使用 -->
<div id="app">
<!-- 在这里就不能使用<newcpn></newcpn> -->
</div>
<div id="app2">
<newcpn></newcpn>
</div>
<script src="../vue.js"></script>
<script>
const cpnC = Vue.extend({
template: ` <div>
<h2>我是一个组件</h2>
</div>`
})
const app = new Vue({
el: '#app',
})
const app2 = new Vue({
el: '#app2',
// 在一个vue实例里注册的组件是局部组件
components: {
// newcpn 注册组件的标签名
newcpn: cpnC
}
})
</script>
③*父组件和子组件:
<div id="app">
<cpn2></cpn2>
<!-- 这里不能使用cpn1组件 因为cpn1既没有在全局注册,又没有在vue实例里注册 -->
</div>
<script src="../vue.js"></script>
<script>
// 1.创建第一个组件(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>这是第一个组件</h2>
</div>
`
})
// 2.创建第二个组件(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>这是第二个组件</h2>
<cpn1></cpn1>
</div>
`,
// 在创建第二个组件时 注册第一个组件并使用
components: {
cpn1:cpnC1
}
})
// 所以可以将vue实例当成最顶层的一个组件 root组件
const app = new Vue({
el: '#app',
components: {
// 在vue实例中注册cpn2组件
cpn2:cpnC2
}
})
</script>
④注册组件的语法糖:
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="../vue.js"></script>
<script>
// 全局组件注册的语法糖
Vue.component('cpn1',{
template: `
<div>
<h2>我是一个全局组件</h2>
</div>
`
})
// 局部组件的语法糖
const app = new Vue({
el: '#app',
components: {
'cpn2': {
template: `
<div>
<h2>我是一个局部组件</h2>
</div>
`
}
}
})
</script>
⑤组件模板抽离方法.:
第一种写法 用script标签 注意类型必须是text/x-template
<div id="app">
<cpn></cpn>
</div>
<!-- 第一种写法 用script标签 注意类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>一个组件</h2>
</div>
</script>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
// 这里直接对应id就行
template:'#cpn'
})
const app = new Vue({
el: '#app',
})
</script>
第二种写法 template标签(推荐) :
<div id="app">
<cpn></cpn>
</div>
<!-- 第二种写法 template标签(推荐)-->
<template id="cpn">
<div>
<h2>一个组件</h2>
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
// 这里直接对应id就行
template:'#cpn'
})
const app = new Vue({
el: '#app',
})
</script>
组件是一个单独功能模块的封装
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
所以组件不可以访问Vue实例里的数据!即使能访问,如果将所有数据都放在vue实例中会显得非常臃肿!
vue组件应该有自己保存数据的地方
⑥*组件的data存放:
组件自己的数据存放在哪里呢?
组件对象也有一个data属性只是这个data属性必须是一一个!函数而且这个函数返回一一个对象,对象内部保存着数据
使用对象保存data可能会出现多个组件共享一个数据,会造成麻烦,而使用函数保存data避免了这个问题
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>{{massage}}</h2>
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn',
// 组件内部可以有data属性,但是data不能像vue实例一样,data不能是对象
// data只能写成一个函数
data(){
return {
massage:'一个组件'
}
}
})
const app = new Vue({
el: '#app',
})
</script>
⑦简单计数器的组件化写法:
<div id="app">
<cpn></cpn>
<cpn></cpn>
<!-- 多个组件不共享一个data对象 -->
</div>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
// 1.注册组件
Vue.component('cpn',{
template: '#cpn',
data(){
return {
counter:0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app',
data: {
massage:'你好啊'
}
})
</script>
二、父子组件的通信:
子组件不能直接引用父组件或者Vue实例里的数据,但是,在开发中,往往一些数据确实需要从上层传递到下层:比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
这个时候,并不会让子组件再次发送一个网络请求 ,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
父子组件通信方法:
1.通过props向子组件传递数据
2.通过事件向父组件发送信息
在开发中,Vue实例和子组件间的通信和父子组件间的通信过程是一样的,所以可以将Vue实例看做一个父组件。
①父传子——props:
props的值有两种方式:
1 字符串数组:数组中的字符串就是传递时的名称。
2对象:对象可以设置传递时的类型,也可以设置默认值等。
<!-- 通过对象传递(主要) -->
<div id="app">
<!-- 注意这里要用v-bind 不然会将"movies"当做字符串赋值-->
<cpn :cmovies="movies" :cmassage="massage"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmassage}}</h2>
</div>
</template>
<script src="../vue.js"></script>
<script>
// 父传子 props
const cpn = {
template:'#cpn',
// props对象写法:
// 这种写法的好处 1.是可以做到类型限制 2.可以提供一些默认值
props: {
cmovies:{
type: Array,
// 类型是对象或者数组时,默认值必须是一个函数
default() {
return []
}
},
cmassage:{
type:String,
// 默认值 在没有传入数据的时候会显示默认值
default:'你好啊',
// 是否必传
required: true
}
}
}
const app = new Vue({
el: '#app',
data: {
massage:'你好啊',
movies:['肖生克的救赎','楚门的世界','时空恋旅人']
},
components: {
// 属性的增强写法
cpn
}
})
</script>
//props对象写法还可以这样写
props: {
cmovies:Array
}
<!-- 通过字符串数组传递 -->
props: ['cmovies','cmassage']
③子传父——自定义事件:
自定义事件的流程:
- 在子组件中;通过$emit()来触发事件。
- 在父组件中,通过v-on来监听子组件事件。
<div id="app">
<!-- 父组件监听事件 -->
<!-- 这里的item-click是自定义事件 没有传参时不会传入事件对象 会把this.$emit('item-click',item)的item传过去 -->
<cpn @item-click="cpnClick"></cpn>
</div>
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}}</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
data() {
return{
categories: [
{id:'AAA',name: 'title1'},
{id:'BBB',name: 'title2'},
{id:'CCC',name: 'title3'},
{id:'DDD',name: 'title4'},
]
}
},
methods: {
btnClick(item){
// 子组件发射自定义事件
// (事件名 想要传给父组件的参数 )
this.$emit('item-click',item)
}
}
}
// 父组件
const app = new Vue({
el: '#app',
data: {
},
// 注册子组件
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick',item)
}
}
})
</script>
④子传父—计数器案例:
在子组件中实现计数器,并把结果传给父组件
<!-- 父组件 -->
<div id="app">
<child-cpn @increment="changeTotal" @decrement="changeTotal">
</child-cpn>
<h2>总数是:{{total}}</h2>
</div>
<template id="child-cpn">
<div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
// 父组件
const app = new Vue({
el: '#app',
data: {
massage:'你好啊',
total:0
},
methods: {
changeTotal(counter) {
this.total = counter;
}
},
components: {
// 注册子组件
// 在子组件的方法中实现计数器
'child-cpn': {
template:'#child-cpn',
data() {
return{
counter:0
}
},
methods: {
increment() {
this.counter++;
// 通过发射事件将counter传到父组件
this.$emit('increment', this.counter)
},
decrement() {
this.counter--;
this.$emit('decrement', this.counter)
}
}
}
}
})
</script>
⑤父子组件通信结合双向绑定案例:
要求:①子组件中input里默认数据来源于父组件中的num;
②子组件中input里输入数据时,父组件num也发生改变;
思路:
首先实现①:
<div id="app">
<!-- step1:父组件将num值传到子组件props的number中 -->
<!-- step2:子组件里,data相当于保存了一份props的数据,当data数据改变时不会影响props,然后input与data双向绑定 -->
<!-- 总结就是 props给了输入框初始值, 而输入框值和data双向绑定,不干扰props -->
<!-- 相当于input里的数据和子组件的data双向绑定,而子组件的data来源于父组件 -->
<cpn :number1="num1"
:number2="num2"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data: {{dnumber1}}</h2>
<!-- <input type="text" v-model="number1"> -->
<!-- 这样写会报错,这里不能这样写 不能直接修改props里的数据,要修改只能用data里的 -->
<input type="text" v-model="dnumber1">
<!-- v-model只能绑定自己的data 不能绑定父类 子类-->
<h2>props:{{number2}}</h2>
<h2>data: {{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
}
}
}
})
</script>
然后实现②:组件中input里输入数据时,父组件num也发生改变;
<div id="app">
<!-- step1.子组件先将父组件传过来的props的值复制到data中,然后将data显示到input中 -->
<!-- step2: 输入框中的数据又会改变子组件的data中的dnumber-->
<!-- step3: 将事件和dnumber发送给父组件,使父组件中的num随dnumber改变-->
<!-- 总结就是:props给了 input初始值,input后面输入的值又会改变data 从而通过发射事件改变num-->
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data: {{dnumber1}}</h2>
<!-- v-model只能绑定自己的data 不能绑定父类 子类-->
<!-- 要想绑定父类只能这样写 -->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props:{{number2}}</h2>
<h2>data: {{dnumber2}}</h2>
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
// 默认传过来的value是string类型
num1change(value) {
this.num1 = parseInt(value);
},
num2change(value) {
this.num2 = parseInt(value);
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
methods: {
num1Input(event) {
this.dnumber1 = event.target.value;
this.$emit('num1change', this.dnumber1)
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2change', this.dnumber2)
}
}
}
}
})
</script>
⑥父访问子—$children:
<!-- 但是开发过程中一般不用这个方法访问子组件对象 -->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// 这里打印出来是一个对象数组
console.log(this.$children);
// 所以可以遍历
for(let c of this.$children){
console.log(c.name);
c.showMessage();
}
}
},
components: {
cpn: {
template: '#cpn',
data() {
return{
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
}
})
</script>
⑦07父访问子—$refs:
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// this.$refs默认为一个空对象
console.log(this.$refs);
// 但是给子组件加上ref属性之后 就会将属性的名字作为对象名展示
console.log(this.$refs.aaa);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return{
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
}
})
</script>
⑧子访问父—parent-root:
<!-- 但是开发过程中一般不用$parent访问父组件对象 -->
<!-- 最大的组件Vue实例 -->
<div id="app">
<cpn></cpn>
</div>
<!-- 第二个组件 -->
<template id="cpn">
<ccpn></ccpn>
</template>
<!-- 最小的组件 -->
<template id="ccpn">
<div>
<h3>我是子组件</h3>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return{
name:'我是cpn组件的name'
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
btnClick() {
// 1.访问我们的父组件 $parent
console.log(this.$parent)
console.log(this.$parent.name)
// 2.访问根组件:$root
console.log(this.$root)
// 打印出的是Vue实例
}
}
}
}
}
}
})
</script>
三、组件化高级使用
①slot插槽的基本使用:
<!--
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot><button>默认值</button></slot>
3.如果有多个值同时放入到组件进行替换,一起作为替换元素
-->
<div id="app">
<cpn><button>按钮</button></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是一个组件</h2>
<slot></slot>
<!-- <slot><button>默认值</button></slot> -->
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template:'#cpn'
}
}
})
</script>
②具名插槽的使用:
<div id="app">
<!-- 这里只能替换没有名字的插槽 -->
<cpn><p>标题</p></cpn>
<!-- 这样就可以替换有名字的插槽了 -->
<cpn><span slot="center">替换中间</span></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边的插槽</span></slot>
<slot name="center"><span>中间的插槽</span></slot>
<slot name="right"><span>右边的插槽</span></slot>
<slot></slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template:'#cpn'
}
}
})
</script>
③编译的作用域:
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<div id="app">
<!-- 这里用的是实例里面的isShow -->
<cpn v-show="isShow"></cpn>
<!-- 因为这是写在Vue实例里的,所以模板里面使用变量时都是在实例里面寻找 -->
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<!-- 这是在组件的模板里 所以使用的是组件里的isShow -->
<button v-show="isShow"></button>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow:true
},
components: {
cpn:{
template: '#cpn',
data() {
return{
isShow: false
}
}
}
}
})
</script>
④作用域插槽的使用:
这样是一个普通的插槽使用:
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<slot>
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpn',
data() {
return{
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
}
}
}
}
})
</script>
怎样改变数据的展示形式呢?
<div id="app">
<!-- 如何使用不同方式展示呢 -->
<!-- 主要父组件获取到子组件里的pLanguage -->
<cpn>
<template v-slot="slot">
<span v-for="item in slot.data">{{item}} - </span>
</template>
</cpn>
<cpn>
<template v-slot="slot">
<span v-for="item in slot.date">{{item}} * </span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 这里相当于把PLanguage传给data了 传给data就可以在父组件里使用 -->
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpn',
data() {
return{
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
}
}
}
}
})
</script>