vue3学习

您所在的位置:网站首页 vue跨层级传递slot vue3学习

vue3学习

#vue3学习 | 来源: 网络整理| 查看: 265

非父子组件之间通信

在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信

主要的方式有

provide 和 inject vuex 事件总线 provide 和 inject

Provide/Inject用于非父子组件之间共享数据, 主要是那些层级嵌套较深的组件之间相互传递数据,

比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容

无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者,

即provide提供的依赖注入可以被看成是long range props

父组件有一个 provide 选项来提供数据

子组件有一个 inject 选项来开始使用这些数据

在整个过程中:

父组件不需要知道哪些子组件使用它 provide 的 property

子组件不需要知道 inject 的 property 来自哪里

IceweE.png

依赖提供者 --- 父组件

import Middle from './components/Middle.vue' export default { name: 'App', components: { Middle }, provide: { // 这里面是无法直接使用this关键字的 // 因为此时的this会找到的是script下的this // 而这个this是undefined,如果需要使用this,这provide需要被设定为是一个函数 msg: 'message in App' } }

依赖使用者 --- 子孙组件

{{ msg }} export default { name: 'Child', inject: ['msg'] }

为了我们可以在provide中正确使用我们的this关键字,我们需要将provide对应的值修改为函数形式

import Middle from './components/Middle.vue' export default { name: 'App', components: { Middle }, // provide中并不会使用this关键字,所以这里不可以使用箭头函数 // 推荐以后将provide直接写成函数形式 provide() { return { // 在vue调用函数的时候,会自动使用call来修正this指向 // 注意: provide中的数据不是响应式的,也就意味着this.msg发生改变的时候, // provide中的msg属性是不会相应发生实时的改变 msg: this.msg } }, // data中并不会使用this关键字,所以这里可以使用箭头函数 data: () => ({ msg: 'message in App' }) }

provide中的数据赋值是一次性的,也就是不是响应式的,如果我们希望实际监听对应状态的改变,而实时修改状态使用者中对应的状态,我们需要使用computed方法

状态提供者 --- App.vue

+1 import Middle from './components/Middle.vue' import { computed } from 'vue' export default { name: 'App', components: { Middle }, provide() { return { // computed是vue3提供的compositeAPI // 作用是可以将this.counter转变为响应式数据,并返回基于参数的对应计算属性 // 参数为一个get方法 counter: computed(() => this.counter) } }, data: () => ({ counter: 0 }) }

状态使用者 --- 子孙组件

{{ counter }} export default { name: 'Child', inject: ['counter'] } 全局事件总线

provide和inject主要是用来祖孙组件之间进行数据的传递,Vuex是用来在多个关联较远的组件之间进行数据传递,

而如果我们希望在一个组件中触发某些事件,在另一个关系较远的组件中监听对应事件并作出相应,就需要使用全局事件总线

npm install mitt

@/utils/emitter.js

import mitt from 'mitt' // 可以使用mitt方法创建多个事件分发器 // 触发和事件的时候,必须使用同一个事件分发器 export const emitter = mitt()

事件触发组件

触发事件 import Middle from './components/Middle.vue' import { emitter } from './utils/emit' export default { name: 'App', components: { Middle }, methods: { handleClick() { // emit(事件名,参数列表) // 多个参数之间使用对象传递,因为emit事件只有两个参数 emitter.emit('emitEvent', { name: 'Klaus' }) } } }

事件响应组件

import { emitter } from '@/utils/emit.js' export default { name: 'Child', created() { // 在组件被创建就需要开始监听对应的事件 emitter.on('emitEvent', param => { console.log(param1) }) } } // 清除所有的事件监听 emitter.all.clear() // 如果需要移除某一个具体的事件监听,那么监听和移除的函数必须是同一个 // 也就是参数2 必须是一个指向某一个具体执行函数的引用地址 function onFoo() {} emitter.on('foo', onFoo) // listen emitter.off('foo', onFoo) // unlisten // *表示监听所有的事件 // 参数1为事件名,参数2为传入的参数 // 触发了几个参数,这个事件就会被执行几次 emitter.on('*', (type, param) => { console.log(type, param) }) emitter.emit('emitEvent', { name: 'Klaus' }) emitter.emit('foo', { name: 'foo' }) emitter.on('*', (type, param) => { console.log(type, param) // => emitEvent {name: "Klaus"} // => foo {name: "foo"} }) 插槽

前面我们会通过props传递给组件一些数据,让组件来进行展示

但是为了让这个组件具备更强的通用性,我们更希望我们组件中有部分的结构也可以由用户进行自定义操作,而不是只能自定义数据

以JD搜索框为例:

IcbXgw.png

这个组件分成三块区域:左边-中间-右边,每块区域的内容是不固定 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等 右边可能是一个文字,也可能是一个图标,也可能什么都不显示

在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽

本质上就是一个占位元素,让外部决定到底显示什么样的元素和内容

如果外部传入了插槽的内容,那么就显示外部传入的内容

如果外部什么数据和元素都没有传入的时候,就不进行任何的渲染

在slot中,我们可以存放任何的内容,无论是自定义的组件,还是元素结构,还是单纯的数据展示

slot使用者

App Component import Child from './components/Child.vue' export default { name: 'App', components: { Child } }

slot声明者

export default { name: 'Child' }

一个不带 name 的slot,会带有隐含的名字 default

默认插槽中的内容 默认插槽中的内容 缺省值

有时候我们希望在使用插槽时,如果没有插入对应的内容,那么我们需要显示一个****默认的内容

这个默认的内容只会在没有提供插入的内容时,才会显示

default value 具名插槽

之前我们的插槽都是没有任何的名字的。那么这个插槽被称之为缺省插槽或默认插槽

此时如果我们界面中有多个缺省插槽

调用者

App Component

使用者

此时每个插槽都会获取到我们插入的内容来显示

因为所有的都是默认插槽,所有都能匹配的上

IcbszR.png

此时我们就需要为我们的插槽起一个名字,这种插槽就被称之为具名插槽

具名插槽顾名思义就是给插槽起一个名字, 元素有一个特殊的 attribute:name

一个不带 name 的slot,会带有隐含的名字 default

调用者

Header Main Footer

定义者

跟 v-on 和 v-bind 一样,v-slot 也有缩写

即把参数之前的所有内容 (v-slot:) 替换为字符 #

Header Main Footer 动态插槽名

目前我们使用的插槽名称都是固定的, 但是有的时候,我们希望插槽的名称也是由外部来具体指定的

此时, 我们可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称

插槽调用者

Main import Child from './components/Child.vue' export default { name: 'App', components: { Child }, data() { return { slotName: 'main' } } }

插槽调用者

export default { name: 'Child', props: { slotName: String } } 渲染作用域

在Vue中有渲染作用域的概念:

父级模板里的所有内容都是在父级作用域中编译的 子模板里的所有内容都是在子作用域中编译的

这就意味着,插槽中的内容虽然是在别的组件中使用的,但是插槽中的内容的编译是在调用插槽的组件中编译的

所以插槽中的变量只能是调用插槽的组件作用域中存在的变量

IcuL6e.png

作用域插槽

因为有渲染作用域存在,所以插槽中使用的变量,只能是调用插槽的组件作用域中存在的变量

但插槽毕竟是在别的组件中进行显示的,也就意味着我们需要使用调用插槽的组件作用域中不存在的变量

此时就可以使用作用域插槽

插槽调用者

{{ slotScope.msg }} import Child from './components/Child.vue' export default { name: 'App', components: { Child } }

插槽定义者

export default { name: 'Child', data() { return { msg: 'Msg in Child Cpn' } } } 独占默认插槽缩写

如果我们的插槽只有默认插槽时,我们就可以将 v-slot 直 接用在组件上,从而省略template标签

{{ slotScope.msg }}

但是,如果我们有默认插槽和具名插槽,那么按照完整的template来编写

因为此时如果直接将插槽写在组件上的时候,vue不知道对应的数据应该给那个slot

多个slot所需要的数据可能是不同的

{{ slotScope.msg }} {{ msg }}


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3