Vuejs组件
一:初识组件
<div id="app">
<!-- 3:使用组件-->
<my-cpn></my-cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1:创建组件
const cpn = Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容</p>
</div>
`
})
// 2:注册组件
Vue.component('my-cpn', cpn) # 使用该方法注册的组件为全局组件,可以在任意vue实例中使用
const app = new Vue({
el: "#app",
data: {
},
})
</script>
1.Vue.extend():
调用Vue.extend()创建的是一个组件构造器。 通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
2.Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
3.组件必须挂载在某个Vue实例下,否则它不会生效。
二:全局组件和局部组件
当我们通过调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件。
const app = new Vue({
el: "#app",
data: {
},
components: {
cpn # 该该vue实例下注册,即为局部组件(只能在当前vue实例下使用)
}
})
三:父组件与子组件
<script>
const cpn1 = Vue.extend({
template: `
<div>
<h2>标题1</h2>
<p>内容1</p>
</div>
`
})
const cpn2 = Vue.extend({
template: `
<div>
<h2>标题2</h2>
<p>内容2</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1 # cpn1组件在cpn2组件中注册,那么cpn2为父组件,cpn1为子组件
}
})
const app = new Vue({
el: "#app",
data: {
},
components: {
cpn2
}
})
</script>
四:注册组件语法糖
在上面注册组件的方式,可能会有些繁琐。Vue为了简化这个过程,提供了注册的语法糖。
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
注册全局组件语法糖:
Vue.component('cpn', {
template: `
<div><h2>标题</h2></div>
`
})
注册局部组件语法糖:
<script>
const app = new Vue({
el: "#app",
data: {
},
components: {
'cpn': {
template:`<div>注册局部组件语法糖</div>`
}
}
})
</script>
五:模板的分离写法
刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容:
- 使用script标签
- 使用template标签
<div id="app">
<cpn></cpn>
<tcpn></tcpn>
</div>
<script type="text/x-template" id="cpn">
<div><h2>抽离出来的模板写法。使用script标签,注意type必须为:text/x-template</h2></div>
</script>
<template id="tcpn">
<div><h2>抽离出来的模板写法。使用template标签</h2></div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: "#app",
data: {
},
template: '#tcpn'
})
</script>
六:组件中的数据
组件不能访问Vue实例中的数据。即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。组件有自己保存数据的地方。
<div id="app">
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('cpn', {
template: '#cpn',
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: "#app",
data: {
}
})
</script>
为什么data在组件中必须是一个函数呢?
- 首先,如果不是一个函数,Vue直接就会报错。
- 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
上面的实例中同一个组件使用了两次,但两个组件中的数据相互不影响:
七:父子组件通信
如何进行父子组件间的通信呢?Vue官方提到
- 通过props向子组件传递数据
- 通过事件向父组件发送消息
7.1 父传子(props)
<div id="app">
<cpn :cmovies="movies"></cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template: `
<div>
<h2>{{cmovies}}</h2>
</div>
`,
props: ['cmovies']
}
const app = new Vue({
el: "#app",
data: {
movies: ['Die hard 1', 'Die hard 2', 'Die hard 3']
},
components: {
cpn
}
})
</script>
7.2 props数据验证
在前面,我们的props选项是使用一个数组。
除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
当我们有自定义构造函数时,验证也支持自定义的类型。验证都支持哪些数据类型呢?
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
props: {
cmovies: Array, # 要求父组件传递过来的必须是数组
}
也可以设置默认值:
props: {
cmoves: {
type: String,
default: 'a'
}
}
但需要注意,如果type为对象或数组时,默认值必须是一个函数:
props: {
cmoves: {
type: Array,
default() {
return []
}
}
}
7.3 props驼峰标识问题
props: {
cMoves: {
type: Array,
default() {
return []
}
}
}
如果上面cMoves是驼峰命名,那么父组件在传递数据时需要注意,只能使用c-moves,使用驼峰方式会报错
<div id="app">
<cpn :c-moves="movies"></cpn>
</div>
7.4 子传父(自定义事件)
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
- 当子组件需要向父组件传递数据时,就要用到自定义事件了。
- 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
- 在子组件中,通过$emit()来触发事件。
- 在父组件中,通过v-on来监听子组件事件。
7.5 父组件访问子组件
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
- 父组件访问子组件:使用$ c h i l d r e n 或 children或 children或refs
- 子组件访问父组件:使用$parent
<div id="app">
<cpn></cpn>
<button @click="btnClick">Button</button>
</div>
<template id="cpn">
<div>
children component
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
methods: {
btnClick() {
console.log(this.$children);
this.$children[0].showMessage()
# 父组件直接访问子组件中的showMessage方法
# this.$children返回一个数组
}
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage() {
console.log('showMessage');
}
}
}
}
})
</script>
通过this.children的缺陷在于需要通过指定数组下标去访问指定子组件中的方法或属性,所以在开发中通常会使用$refs来访问子组件
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aa"></cpn> # 给需要访问的组件添加ref属性
<button @click="btnClick">Button</button>
</div>
父组件中通过this.$refs.ref属性名来访问子组件
methods: {
btnClick() {
this.$refs.aa.showMessage();
}
},
7.6 子组件访问父组件
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>children component</h2>
<button @click="btnClick">Button</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
components: {
cpn: {
template: '#cpn',
methods: {
btnClick() {
console.log(this.$parent.message);
}
}
},
}
})
</script>
开发中组件中可能会有多层嵌套,无论在哪个子组件中想要直接访问根组件,那么就使用$root
components: {
cpn: {
template: '#cpn',
methods: {
btnClick() {
console.log(this.$root.message);
}
}
},
}
7.7 非父子组件通信
如果是非父子关系,非父子组件关系包括多个层级的组件,也包括兄弟组件的关系,它们之间要如何互相通信呢?
在Vue1.x的时候,可以通过 dispatch和broadcast完成
- $dispatch用于向上级派发事件
- $broadcast用于向下级广播事件
但是在Vue2.x都被取消了
- 在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
- 但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
- 并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。
八:插槽(slot)
8.1 基本使用
定义插槽:
<template id="cpn">
<div>
<h2>children component</h2>
<slot></slot>
# <slot><button>Button</button></slot> 定义插槽默认标签
</div>
</template>
使用插槽:
<div id="app">
# 可以在cpn组件内部使用任意标签来替换slot标签
<cpn><button>插槽,按钮</button></cpn>
<cpn><p>插槽,内容</p></cpn>
</div>
8.2 具名插槽
<div id="app">
<cpn>
<span slot="center">Middle</span>
</cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>Left</span></slot>
<slot name="center"><span>Center</span></slot>
<slot name="right"><span>Right</span></slot>
</div>
</template>
8.3 作用域插槽
官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供
先提一个需求:子组件中包括一组数据,比如:pLanguages: [‘JavaScript’, ‘Python’, ‘Swift’, ‘Go’, ‘C++’],需要在多个界面进行展示:
- 某些界面是以水平方向一一展示的,
- 某些界面是以列表形式展示的,
- 某些界面直接展示一个数组
内容在子组件,希望父组件告诉我们如何展示,怎么办呢?利用slot作用域插槽就可以了
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}} - </span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<!-- :data,data名可随意取 -->
<slot :data="pLanguages">
<ul><li v-for="item in pLanguages">{{item}}</li></ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: 'Hello'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
}
}
}
}
})
</script>