8.31_vue 组件/插槽
Vue
1.component(组件)
父子组件:父组件只能用自己组件的data、methods等东西,子组件也只能用自己组件定义的data、methods等东西。父子组件想要用彼此的数据,只有两种方法,一种是父子间通信,一种是使用如$children、$refs等来进行父子访问。
全局组件
所有实例(如id=app是一个Vue实例)都能使用全局组件
<body>
<div id="app">
<!-- 调用组件 -->
<new-component></new-component>
</div>
<!-- 说到底,Vue基于JavaScript -->
<script>
// 全局注册组件
// 命名方式一定要使用短横线,否则在转换为DOM后调用的时候无法识别组件名而报错
// new-component为自定义的组件名,template中写组件呈现的内容
Vue.component('new-component', {
template: `
<h1>自定义组件</h1>
`
})
var app = new Vue({
//为此Vue实例提供挂载元素,可以是CSS选择器,也可以是HTML元素。如此实例中挂载到了id为app的div上,从而该html元素可以使用此Vue中的data、methods等
el: '#app',
data: {
},
})
</script>
</body>
局部组件
局部组件在哪里注册,就只能在哪里使用。
<body>
// 根组件,是do-child2的父组件 <div id="app"> <!-- 调用组件 --> <do-child2></do-child2> </div> <!-- 说到底,Vue基于JavaScript --> <script> // 局部组件定义(子组件) var child1 = { template: ` <h1>局部组件1</h1> ` } // 父组件 var child2 = { // template中一定要有一个根元素,加一个div就行 // 这里调用do-child2执行的是child1,说明子组件中组件注册覆盖了父组件中的组件注册 template: ` <div> <h1>局部组件2</h1> <do-child2></do-child2> </div> `, components: { // 在第二个子组件中注册子组件一,可以使用子组件一 'do-child2': child1 } } var app = new Vue({ //为此Vue实例提供挂载元素,可以是CSS选择器,也可以是HTML元素。如此实例中挂载到了id为app的div上,从而该html元素可以使用此Vue中的data、methods等 el: '#app', data: { }, components: { // 引号中的child为调用该组件的组件名 // 子组件只能在父组件中使用'do-child2': child2 } }) </script> </body>
组件注册分离写法
<body>
<div id="app">
<new></new>
<do-child></do-child>
</div>
<!-- 组件分离写法:html语句写在template标签中,通过id和组件联系 -->
<template id="new-component">
<div>
<h1>123</h1>
</div>
</template>
<!-- 说到底,Vue基于JavaScript -->
<script>
// 全局组件注册
Vue.component('new', {
template: '#new-component'
})
// 局部组件注册
var child = {
template: '#new-component'
}
var app = new Vue({
//为此Vue实例提供挂载元素,可以是CSS选择器,也可以是HTML元素。如此实例中挂载到了id为app的div上,从而该html元素可以使用此Vue中的data、methods等
el: '#app',
data: {
text: ''
},
components: {
'do-child': child
}
})
</script>
</body>
父组件给子组件传值(props)
props是单向绑定,即父组件中变化时可以传递给子组件,反过来则不行
<body>
<div id="app">
<!-- 绑定text数据 -->
<input type="text" v-model="text">
<br>
<!-- 父组件传值 -->
<child :message="text" :message1="123"></child>
</div>
<!-- 说到底,Vue基于JavaScript -->
<script>
Vue.component('child', {
// 使用数组声明props(不常用,多少版本之后可能有bug)
props: ['message', 'message1'],
// 接收父组件传值
template: `
<span>{{message}}+{{message1}}</span>
`
})
var app = new Vue({
//为此Vue实例提供挂载元素,可以是CSS选择器,也可以是HTML元素。如此实例中挂载到了id为app的div上,从而该html元素可以使用此Vue中的data、methods等
el: '#app',
data: {
text: ''
},
components: {
}
})
</script>
</body>
props数据验证
支持的数据类型:String、Number、Boolean、Array、Object、Date、Function、Symbol
<div id="app">
<!-- 绑定text数据 -->
<input type="text" v-model="text">
<br>
<!-- 父组件传值 -->
<child :message="text"></child>
</div>
<!-- 说到底,Vue基于JavaScript -->
<script>
Vue.component('child', {
// 使用对象声明props(常用)
props: {
// 类型限制
message: [String, Number],
message1: {
type: String,
// 当父组件没有传值的时候,显示此默认值,否则将默认值替换
default: '默认显示',
// 布尔值required,设定父组件此属性必须传值,不传报错
// required: true
},
}, // 接收父组件传值 template: ` <span>{{message}}+{{message1}}</span> ` })
子传父
使用自定义事件
<body>
<div id="app">
<!-- 3.在这里监听子组件发射出来的事件,同时触发父组件事件 -->
<!-- 这里默认会传递参数,所以reflect不用加括号写参数,但是下面methods中的
reflect函数要加括号加参数来接收传递到的数据 -->
<c-t-f @item-click="reflect"></c-t-f>
</div>
<template id="c-t-f">
<div>
<!-- 1.绑定点击事件,将点击的哪个按钮传递给子组件事件并发射出去 -->
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script>
const childToFather = {
template: '#c-t-f',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'}
]
}
},
methods: {
btnClick(item) {
// 2.子组件发射一个事件(事件名随便起),后面跟需要携带的参数
this.$emit('item-click', item)
}
}
}
var app = new Vue({
el: '#app',
data: {
text: ''
},
components: {
"c-t-f": childToFather
},
methods: {
reflect(item) {
console.log("父组件已接收到"+item.name)
}
}
})
</script>
</body>
父子组件的访问方式
有时候需要父组件直接访问子组件或是子组件直接访问父组件
父组件访问子组件:$children或$refs(this.$children是一个数组类型,包含所有子组件对象)
<body>
<div id="app">
<f-t-c></f-t-c>
<f-t-c ref="aaa"></f-t-c>
<f-t-c></f-t-c>
<button @click="btnClick">点我访问子组件</button>
</div>
<template id="ftc">
<div>
<span>我是子组件</span>
</div>
</template>
<script>
const fatherToChild = {
template: '#ftc',
methods: {
showMessage() {
console.log("我被父组件访问到了!")
}
},
data() {
return {
name: '我是子组件!'
}
}
}
var app = new Vue({
el: '#app',
data: {
},
components: {
'f-t-c': fatherToChild
},
methods: {
btnClick() {
// 可以看一下this.$children具体是什么
console.log(this.$children)
// 通过this.$children[0]访问到子组件中的内容。其中因为$children是数组,
// [0]代表这是父组件中的第一个子组件
this.$children[0].showMessage()
console.log(this.$children[0].name)
// 如果使用下标值来选择子组件,当父组件中添加其他子组件的时候需要来回修改,很不方便
// 所以一般使用$refs
console.log(this.$refs.aaa)
this.$refs.aaa.showMessage()
console.log(this.$refs.aaa.name)
}
}
})
</script>
</body>
子组件访问父组件:$parent(访问根组件$root)
但是如果这样做的话会降低子组件的复用性,因为有的父组件不一定有子组件需要获取的数据。因此$parent在实际开发中用的非常少;$root用的也非常少,因为根组件的Vue实例几乎不放任何实质性的东西
<body>
<div id="app">
<c-t-f></c-t-f>
</div>
<template id="ctf">
<div>
<span>我是子组件</span>
<button @click="btnClick">点击访问父组件</button>
</div>
</template>
<script>
const childToFather = {
template: '#ctf',
// 这次因为要访问父组件,所以在子组件中做监听
methods: {
btnClick() {
// 看一下$parent
console.log(this.$parent)
console.log(this.$parent.name)
// 访问根组件$root
console.log(this.$root)
console.log(this.$root.name)
}
},
data() {
return {
}
}
}
var app = new Vue({
el: '#app',
data: {
name: '父组件被访问到!'
},
components: {
'c-t-f': childToFather
},
methods: {
}
})
</script>
</body>
2.slot(插槽)
组件插槽是为了让封装的组件更具拓展性,可以让使用者决定组件呈现的内容。最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽
插槽基本使用:
<div id="app"> <!-- demo元素内部的所有元素(无论几个)都会放到组件定义中的插槽的位置 --> <demo>
<button>123</button>
<h1>456</h1>
<h2>789</h2>
</demo> </div> <!-- 组件定义 --> <template id="demo"> <div> <h2>我是组件</h2> <slot></slot> </div> </template>
默认插槽:
<div id="app"> <!-- 如果组件调用没有传递新的内容给组件,那么就会调用默认值插槽 否则会将使用者自定义的内容覆盖到插槽上 --> <demo></demo> <demo>哈哈哈</demo> </div> <!-- 组件定义 --> <template id="demo"> <div> <h2>我是组件</h2> <!-- 相当于在插槽中定义了默认值 --> <slot><button>123</button></slot> </div> </template>
具名插槽:
// 父组件
<div id="app"> <!-- 通过slot指定要将该元素放到哪个插槽中 --> <demo><span slot="center">具名插槽</span></demo> </div> <!-- 子组件定义 --> <template id="demo"> <div> <h2>我是组件</h2> <!-- 定义三个具名插槽 --> <slot name="left">左边</slot> <slot name="center">中间</slot> <slot name="right">右边</slot> </div> </template>
作用域插槽
是为了解决这样一种情景:父组件对子组件的数据展示不满意,希望拿到子组件的数据自己来展示。即父组件替换插槽的标签,但是内容由子组件来提供
<div id="app"> <!-- 因为父组件对应的变量作用域只有实例变量,无法去到子组件的变量作用域获取变量 所以父组件想要获取子组件的数据重新展示,只能使用作用域插槽 --> <demo> <!-- 2.5.x版本之后不强制使用template,可以使用如div等元素,为了兼容性,此处使用template --> <!-- 2.使用slot-scope来拿到子组件的slot引用,引号里的名字随便起。其中slot="first"是具名插槽,用来指定此template中的内容替换哪一个slot --> <template slot-scope="slotttt" slot="first"> <!-- 3.然后就可以拿到slot中定义的data(最主要的是组件中设置的数据绑定,根据绑定的数据来引用,slot-scope可以理解成拿到组件的slot引用)指向的pLanguages来使用了 --> <!-- 成功从父组件访问到子组件的数据,并进行修改后放入到对应具名插槽中 --> <span v-for="item in slotttt.data">{{item}}-</span> </template> <!-- 作用域插槽加具名插槽可以实现父组件对多组子组件数据的使用。这里的num只是一个名字,可以随便起,目的是拿到组件的slot的引用,其中data1才是真正的子组件中的num变量的引用 --> <template slot-scope="num" slot="second"> <span v-for="item in num.data1">{{item}}-</span> </template> </demo> </div> <!-- 组件定义 --> <template id="demo"> <div> <!-- 1.首先slot要引用到需要使用的数据,:data中的data是随便起的名字,之后父组件调用会用到 --> <!-- 这个插槽其实就是一个拿数据的作用 --> <slot :data="pLanguages" name="first"> <!-- 默认值就会被替换掉 --> <ul> <li v-for="item in pLanguages">{{item}}</li> </ul> </slot> <slot :data1="num" name="second"> <ul> <li v-for="item in pLanguages">{{item}}</li> </ul> </slot> </div> </template> <!-- 说到底,Vue基于JavaScript --> <script> var app = new Vue({ el: '#app', //为此Vue实例提供挂载元素,可以是CSS选择器,也可以是HTML元素。如此实例中挂载到了id为app的div上,从而该html元素可以使用此Vue中的data、methods等 // 父组件变量作用域 data: { }, components: { demo: { template: '#demo', // 子组件变量作用域 data() { return { pLanguages: ['JavaScript', 'C++', 'Java'], num: ['123', '456', '789'] } } } } }) </script>