vue3学习 |
您所在的位置:网站首页 › vue跨层级传递slot › vue3学习 |
非父子组件之间通信
在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信 主要的方式有 provide 和 inject vuex 事件总线 provide 和 injectProvide/Inject用于非父子组件之间共享数据, 主要是那些层级嵌套较深的组件之间相互传递数据, 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容 无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者, 即provide提供的依赖注入可以被看成是long range props 父组件有一个 provide 选项来提供数据 子组件有一个 inject 选项来开始使用这些数据 在整个过程中: 父组件不需要知道哪些子组件使用它 provide 的 property 子组件不需要知道 inject 的 property 来自哪里 依赖提供者 --- 父组件 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搜索框为例: 这个组件分成三块区域:左边-中间-右边,每块区域的内容是不固定 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等 右边可能是一个文字,也可能是一个图标,也可能什么都不显示在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽 本质上就是一个占位元素,让外部决定到底显示什么样的元素和内容 如果外部传入了插槽的内容,那么就显示外部传入的内容 如果外部什么数据和元素都没有传入的时候,就不进行任何的渲染 在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使用者 此时每个插槽都会获取到我们插入的内容来显示 因为所有的都是默认插槽,所有都能匹配的上 此时我们就需要为我们的插槽起一个名字,这种插槽就被称之为具名插槽 具名插槽顾名思义就是给插槽起一个名字, 元素有一个特殊的 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中有渲染作用域的概念: 父级模板里的所有内容都是在父级作用域中编译的 子模板里的所有内容都是在子作用域中编译的这就意味着,插槽中的内容虽然是在别的组件中使用的,但是插槽中的内容的编译是在调用插槽的组件中编译的 所以插槽中的变量只能是调用插槽的组件作用域中存在的变量 作用域插槽因为有渲染作用域存在,所以插槽中使用的变量,只能是调用插槽的组件作用域中存在的变量 但插槽毕竟是在别的组件中进行显示的,也就意味着我们需要使用调用插槽的组件作用域中不存在的变量 此时就可以使用作用域插槽 插槽调用者 {{ 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 |