一:插槽slot
Vue 实现了一套内容分发的 API, 将 <slot>
元素作为承载分发内容的出口。
简单的说就是将一段html元素作为参数传递到自定义组件中。
使用<slot></slot>
元素来预定义插槽的位置,后面可以将内容直接插在插槽位置上即可。
<slot></slot>
中也可以有默认值,当使用组件没有提供插槽内容时就会使用默认值。
官方文档:https://cn.vuejs.org/v2/api/#slot
1.1 默认插槽
默认插槽没有名字的插槽,一般默认插槽只有一个,自定义一个组件(导航链接组件NavLink.vue),组件的内容比较简单只有一个a标签,在a标签中使用了<slot></slot>
元素。
<template>
<a v-bind:href="'#' + url">
<slot>插槽默认值:当没有提供插槽内容时使用这里的值</slot>
</a>
</template>
<script>
export default {
name: 'NavLink',
props: {
url: String
}
}
</script>
<template>
<div>
<nav-link url="/foo">
<span>Navigation Link To Foo Component</span>
<br>
<span> 插槽的作用就是将一段html元素(也可以是其它自定义的组件)传参到自定义的组件中, 然后将slot部分替换掉 </span>
</nav-link>
<br><br>
<nav-link url="/foo"></nav-link>
</div>
</template>
<script>
import NavLink from './NavLink'
export default {
name: 'HelloWorld',
components: {NavLink}
}
</script>
查看源码可以看到<nav-link>
标签内的内容传递到NavLink组件,并将<slot></slot>
元素给替换掉了。简单来说slot就是组件的一种传参方式,只不过传的是html元素。
1.2 具名插槽
当组件中有多个插槽位置时,可以给每个插槽命一个名字,在使用时指定插槽名称即可。
使用组件时在传递插槽内容时可以将slot属性作用在<template>
标签上,也可以作用在普通标签上。
NavLink.vue
<template>
<a v-bind:href="'#' + url">
<slot name="slot1"></slot>
<slot name="slot2"></slot>
<slot></slot>
</a>
</template>
<script>
export default {
name: 'NavLink',
props: {
url: String
}
}
</script>
HelloWorld.vue
<template>
<div>
<nav-link url="/foo">
<template slot="slot1">
Navigation
</template>
<h1 slot="slot2">To</h1>
<span> Foo </span>
<span> Component</span>
</nav-link>
</div>
</template>
<script>
import NavLink from './NavLink'
export default {
name: 'HelloWorld',
components: {NavLink}
}
</script>
-
<template>
中的内容会被当做普通字符串 - 插槽位置即可以匿名(默认插槽)也可以命名(具名插槽),传递插槽内容时需要指定名称,如果没有指定名称剩下没有匹配到的内容将全部作为匿名插槽的内容
1.3 作用域插槽 slot-scope
作用域插槽:父组件(使用自定义组件的组件,即HelloWorld.vue)访问子组件(自定义组件,即TodoList.vue)中的值。
- 作用域插槽一般用在组件中使用v-for循环的。
- slot-scope即可用于
<template>
元素上也可以用于普通元素上。
TodoList.vue
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
<slot v-bind:item="todo">
{{ todo.text }}
</slot>
</li>
</ul>
</template>
<script>
export default {
name: 'TodoList',
props: ['todos']
}
</script>
item:用于定义一个外层组件能够通过scope访问的属性名,随便写。item的值就是v-for中的todo。
HelloWorld.vue
<template>
<div>
<todo-list :todos="todos">
<template slot-scope="scope">
<span v-if="scope.item.isComplete">√</span>
{{ scope.item.text }}
</template>
</todo-list>
</div>
</template>
<script>
import TodoList from './TodoList'
export default {
name: 'HelloWorld',
components: {TodoList},
data () {
return {
todos: [
{id: 1, text: '做饭', isComplete: true},
{id: 2, text: '吃饭', isComplete: true},
{id: 3, text: '刷锅', isComplete: false}
]
}
}
}
</script>
注意:scope和slot-scope功能差不多,老版本中使用scope属性,scope属性只能用在<template>
中,在新版本中使用slot-scope来替代scope,slot-scope可以用在任意元素上。新版本推荐使用slot-scope。
二:路由导入组件
组件导入方式
- import方式: 使用import引入,当项目打包时路由里的所有component都会打包在一个js中,造成进入首页时,需要加载的内容过多,时间相对比较长。
- require方式: 用require这种方式引入的时候,会将你的component分别打包成不同的js,加载的时候也是按需加载,只用访问这个路由网址时才会加载这个js。
HelloWorld.vue
<template>
<div style="width: 300px">
HelloWorld Vue
</div>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
Foo.vue
<template>
<div>
Foo Component
</div>
</template>
<script>
export default {
name: 'Foo'
}
</script>
src/router/index.js import方式
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Foo from '@/components/Foo'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
component: HelloWorld
},
{
path: '/foo',
component: Foo
}
]
})
src/router/index.js require方式
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
component: resolve => require(['@/components/HelloWorld'], resolve)
},
{
path: '/foo',
component: resolve => require(['@/components/Foo'], resolve)
}
})
我们看到使用require方式,当访问哪个路由就会加载哪个js文件,按需加载,懒加载。
三:vm.$emit( eventName, […args] )
emit 用于自定义组件事件。
this.$emit('handleClick', value)
用于注册自定义事件,事件名称为“handleClick”,参数为value。
1. Child.vue
<template>
<div>
<button @click="handleChildClick('mengday')">自定义组件事件(子组件调用父组件的方法、子组件像父组件传值)</button>
</div>
</template>
<script>
export default {
name: 'Emit',
methods: {
handleChildClick (value) {
this.$emit('handleClick', value)
}
}
}
</script>
2. Parent.vue
child元素中的handleClick事件就是Child.vue中使用emit注册的事件名称,值为当前实例的方法。
<template>
<div>
{{username}}
<child @handleClick="handleClick"></child>
</div>
</template>
<script>
import Child from '@/components/Child'
export default {
name: 'Parent',
components: {Child},
data () {
return {
username: '--'
}
},
methods: {
handleClick (value) {
console.log('处理业务逻辑...')
// 赋值:达到子组件向父组件传参的目的
this.username = value
}
}
}
</script>
3. src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Parent from '@/components/Parent'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/parent',
component: Parent
}
]
})
当点击按钮时Child会调用自己的方法handleChildClick,进而向子组件注册事件handleClick,而父组件已经定义了handleClick的值为父组件的handleClick方法,从而调用该方法。
emit: 准确的来说是用来自定义组件事件的,很多博客用来把它当成子组件像父组件传值使用,当然子组件都能调用父组件的方法了,把参数传进去就起到了子组件向父组件传值的作用,这只是emit的一种作用,仅仅把emit作为子组件向父组件传值的描述不够准确,既然子组件能够调用父组件的方法,方法中当然也可以写一些业务逻辑,所以为我认为emit用来自定义组件事件更加准确些。
父组件向子组件传值:通过props来实现
子组件向父组件传值:通过emit来实现
兄弟组件之间互相传值:通过Vuex来实现
四:$on(‘eventName’, function(args){ })
- $on(‘eventName’, function(args){ }) 用于注册事件
- $emit(‘event’, arg) 用于触发事件
<template>
<div>
<button @click="handleClick">自定义事件</button>
</div>
</template>
<script>
export default {
name: 'Parent',
created: function () {
// 先注册事件
this.$on('customEvent', function (msg) {
console.log('自定义事件:' + msg)
})
},
methods: {
handleClick () {
// 触发事件:调用事件对应的处理函数
this.$emit('customEvent', '参数')
}
}
}
</script>
五:$parent
$parent: 获取父组件的引用。拿到父组件的引用就可以操作父组件了,如修改父组件中的data,调用父组件中的方法,或者通过在子组件调用父组件用于子组件向父组件传值等。
Child.vue
<template>
<div>
<button @click="handleClick">this.$parent</button>
</div>
</template>
<script>
export default {
name: 'Child',
methods: {
handleClick () {
this.$parent.msg = 'Child'
this.$parent.foo()
}
}
}
</script>
<template>
<div>
{{ msg }}
<child></child>
</div>
</template>
<script>
import Child from '@/components/demo/Child'
export default {
name: 'Parent',
components: {Child},
data () {
return {
msg: 'Parent'
}
},
methods: {
foo () {
console.log('父组件方法...')
}
}
}
</script>