组件化是 Vue.js 中的重要思想
组件化的使用分为三个步骤
Vue.extend()
方法创建组件构造器
Vue.component()
方法 注册组件
使用组件
全局组件
<div id="app">
// 3.使用组件
<cpn></cpn>
</div>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
标题 1
内容 1111
`
})
// 2.注册组件
Vue.component('cpn', cpnC)
const app = new Vue({
el: '#app',
data: {},
methods: {}
})
</script>
标题 1
内容 1111
局部组件
<div id="app">
<cpn></cpn> //正常使用组件
</div>
<div id="app2">
<cpn></cpn> //报错
</div>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
标题 1
内容 1111
`
})
const app = new Vue({
el: '#app',
data: {},
// 2.注册组件
components: {
cpn: cpnC
}
})
const app2 = new Vue({
el: '#app2',
data: {},
methods: {}
})
</script>
标题 1
内容 1111
<script>
//子组件
const cpnC1 = Vue.extend({
template: `
标题 1
内容 1111
`
})
//父组件
const cpnC2 = Vue.extend({
template: `
标题 2
内容 2222
`,
components: {
cpn1: cpnC1
}
})
//root组件
const app = new Vue({
el: '#app',
data: {},
methods: {},
components: {
cpn: cpnC2
}
})
</script>
<div id="app">
<cpn></cpn>
</div>
标题 1
内容 1111
标题 2
内容 2222
语法糖主要是省去了调用Vue.extend()
的步骤,而是可以直接用一个对象来代替
全局组件
<div id="app">
// 3.使用组件
<cpn></cpn>
</div>
<script>
Vue.component('cpn', {
template: `
标题 1
内容 1111
`
})
const app = new Vue({
el: '#app',
data: {},
methods: {}
})
</script>
标题 1
内容 1111
局部组件
<div id="app">
<cpn></cpn> //正常使用组件
</div>
<div id="app2">
<cpn></cpn> //报错
</div>
<script>
const app = new Vue({
el: '#app',
data: {},
methods: {},
components: {
'cpn': {
template: `
标题 1
内容 1111
`
}
}
})
</script>
标题 1
内容 1111
刚才我们通过语法糖简化了 Vue 组件中的注册过程,另外还有一个地方的写法比较繁琐,就是 template 模块写法。
components: {
'cpn': {
template: `
标题 1
内容 1111
`
}
}
如果我们能将 template 模块中的 HTML分离出来,然后挂载到对应的组件上,结构必然会更加清晰。
<div id="app">
<cpn></cpn>
</div>
<script>
Vue.component('cpn',{
template:'#cpn'
})
const app = new Vue({
el: '#app',
data: {},
methods: {}
})
</script>
标签<script type="text/x-template" id="cpn">
<h2>我是标题</h2>
</script>
标签<template id="cpn">
<h2>我是标题2</h2>
</template>
我是标题
组件对象也有一个 data
属性(也可以有 methods 等属性),只是这个 data 属性必须是一个函数
,而且这个函数返回一个对象,对象内部保存着数据。
关于组件中的 data 为什么是函数,原理可以参考视频p57,老师讲的非常透彻易懂
<template id="cpn">
<h2>{{msg}}</h2>
</template>
<script>
Vue.component('cpn', {
template: '#cpn',
data() {
return {
msg: 'Hello Vue'
}
}
})
const app = new Vue({
el: '#app'
})
</script>
Hello Vue
子组件是不能引用父组件或者 Vue 实例中的数据的,但在开发中往往一些数据需要从上层传递到下层,那么如何进行父子组件间的通信呢?
通过props
向子组件传递数据
<body>
<div id="app">
<!--用 v-bind 才会把 movies 当做变量而不是字符串-->
<cpn :cmovies="movies" :cmsg="msg"></cpn>
</div>
</body>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
</div>
</template>
<script>
//子组件
const cpn = Vue.extend({
template: '#cpn',
//父传子:props
// props: ['cmovies', 'cmsg']
props: {
// 1.类型限制
cmovies: Array,
cmsg: String,
// 2.提供一些默认值
cmsg: {
type: String,
default: 'child', //默认值
required: true //必须传入值
},
// 类型是对象或者数组时,默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
}
})
//root组件
const app = new Vue({
el: '#app',
data: {
msg: 'hello',
movies: ['dodo', 'bibi', 'pepe']
},
methods: {},
components: {
cpn: cpn
}
})
</script>
使用props
时,如果使用驼峰标识需要进行转化
<body>
<div id="app">
<cpn :c-info="info"></cpn>
</div>
<template id="cpn">
<div>{{cInfo}}</div>
</template>
</body>
<script>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return {}
}
}
}
}
//root组件
const app = new Vue({
el: '#app',
data: {
info: {
id: '1',
name: 'dodo',
age: '11'
}
},
methods: {},
components: {
cpn: cpn
}
})
</script>
{ “id”: “1”, “name”: “dodo”, “age”: “11” }
通过事件
向父组件发送消息
自定义事件流程:
.$emit()
来触发事件v-on
来监听子组件事件简单列表案例
<body>
<!--父组件模板-->
<div id="app">
<!--监听事件(默认自动传递参数)-->
<cpn @itemclick="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
</body>
<script>
//子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: '1', name: '列表 1'},
{id: '2', name: '列表 2'},
{id: '3', name: '列表 3'},
{id: '4', name: '列表 4'}
]
}
},
methods: {
btnClick(item) {
//发射事件(名称,事件)
this.$emit('itemclick', item)
}
}
}
//root组件
const app = new Vue({
el: '#app',
data: {},
methods: {
cpnClick(item) {
console.log(item)
}
},
components: {
cpn: cpn
}
})
</script>
<body>
<!--父组件模板-->
<div id="app">
<!--通过v-on监听事件。发生两个事件时,调用同一个函数changeTotal-->
<cpn @increment="changeTotal" @decrement="changeTotal"></cpn>
<h2>点击次数:{{total}}</h2>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
</body>
<script>
//子组件
const cpn = {
template: '#cpn',
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
//子组件发出事件
this.$emit('increment', this.count)
},
decrement() {
this.count--;
this.$emit('decrement', this.count)
}
}
}
//root组件
const app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
changeTotal(count) {
this.total = count
}
},
components: {
cpn: cpn
}
})
</script>
<div id="app">
<cpn :number1="num1" :number2="num2" @number1change="num1change" @number2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!--直接双向绑定会报错,不要直接绑定 props ,使用 data 或 computed 绑定-->
<!--<input type="text" v-model="dnumber1">-->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>{{number2}}</h2>
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
// 初始化
num1: 1,
num2: 2
},
methods: {
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) {
// 1.将 input 中的 value 赋值到 dnumber 中
this.dnumber1 = event.target.value;
// 2.为了让父组件可以修改值,发出一个事件
this.$emit('number1change', this.dnumber1)
// 3.同时修饰 dnumber2 的值
this.dnumber2 = this.dnumber1 * 100;
this.$emit('number2change', this.dnumber2)
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('number2change', this.dnumber2)
this.dnumber1 = this.dnumber2 / 100;
this.$emit('number1change', this.dnumber1)
}
}
}
}
})
</script>
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。
$children
或$refs
(常用)<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">button</button>
</div>
<template id="cpn">
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello,Vue'
},
methods: {
btnClick() {
// 1.$children
// console.log(this.$children)
// console.log(this.$children[0].showmsg())
// 2.$refs -> 对象类型,默认是一个空的对象
console.log(this.$refs.aaa.name)
}
},
components: {
cpn: {
template: '#cpn',
data(){
return{
name:'this is component'
}
},
methods: {
showmsg() {
console.log('msg')
}
}
}
}
})
</script>
$parent
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子子组件</h2>
<button @click="btnClick">button</button>
</div>
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello,Vue'
},
methods: {},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: 'my name is cpn'
}
},
components: {
ccpn: {
template: '#ccpn'
,
methods: {
btnClick() {
// 1.访问父组件 $parent
console.log(this.$parent.name) // my name is cpn
// 2.访问根组件 $root
console.log(this.$root.msg) // Hello,Vue
}
}
}
}
}
}
})
</script>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn><span>哈哈哈</span></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn</h2>
<!--默认值-->
<slot>
<button>button</button>
</slot>
</div>
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello,Vue'
},
components: {
cpn: {
template: '#cpn'
}
},
methods: {}
})
</script>
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
<div id="app">
<cpn v-show="isShow"></cpn>
</div>
<template id="cpn">
<div>
<p>我是子组件</p> //true
<button v-show="isShow">按钮</button> //false
</div>
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
isShow: true,
msg: 'Hello,Vue'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
}
}
})
</script>
父组件替换插槽的标签,但内容由子组件来提供
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' - ')}} </span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello,Vue'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C', 'Java', 'Python', 'Swift']
}
},
created() {
this.pLanguages.join(' - ')
}
}
},
methods: {}
})
</script>
参考视频:小码哥 Vue 视频教程