您当前的位置:首页 > 电脑百科 > 程序开发 > 前端

vue3源码分析——实现slots

时间:2022-06-21 09:51:49  来源:  作者:aLittleTwinkle

引言

<<往期回顾>>

  1. vue3源码分析——rollup打包monorepo
  2. vue3源码分析——实现组件的挂载流程
  3. vue3源码分析——实现props,emit,事件处理等

本期来实现, slot——插槽,分为普通插槽,具名插槽,作用域插槽,所有的源码请查看

正文

在 模板中使用插槽的方式如下:

<todo-button>
  Add todo
</todo-button>
复制代码

在template中的内容最终会被complie成render函数,render函数里面会调用h函数转化成vnode,在vnode的使用方法如下:

render() {
    return h(TodoButton, {}, this.$slots.default)
  },
复制代码

看完slots的基本用法,一起来实现个slots,方便自己理解slots的原理哦!

实现基本的用法

使用slots的地方是this.slots,并且调用的属性是default,那么slots,并且调用的属性是default,那么slots则是一个对象,对象里面有插槽的名称,如果使用者没有传递,则可以通过default来进行访问

测试用例

attention!!! 由于测试的是dom,需要先写入html等,在这里需要先创建对应的节点

 let AppElement: Element;
  beforeEach(() => {
    appElement = document.createElement('div');
    appElement.id = 'app';
    document.body.appendChild(appElement);
  })
  afterEach(() => {
    document.body.innerHTML = '';
  })
复制代码

本案例的测试正式开始

 test('test basic slots', () => {
 // 子组件Foo
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' }, [h('p', {}, this.count), renderSlots(this.$slots)]);
      }
    }

    const app = createApp({
      render() {
        return h('div', { class: 'contAIner' }, [
          h(Foo, { count: 1 }, { default: h('div', { class: 'slot' }, 'slot1') }),
          h(Foo, { count: 2 }, { default: [h('p', { class: 'slot' }, 'slot2'), h('p', { class: 'slot' }, 'slot2')] }),
        ])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);
    // 测试挂载的内容是否正确
    const container = document.querySelector('.container') as HTMLElement;
    expect(container.innerHTML).toBe('<div class="foo"><p>1</p><div><div class="slot">slot1</div></div></div><div class="foo"><p>2</p><div><p class="slot">slot2</p><p class="slot">slot2</p></div></div>'
    )
  })
复制代码

需求分析

通过上面的测试用例,可以分析以下内容:

  1. 父组件使用子组件传入插槽的方式是在h的第三个参数,并且传入的是一个对象,value的值可以是对象,或者是数组
  2. 子组件中使用插槽的时候,是在this.$slots中获取的
  3. 并且还实现了一个renderSlot的方法,renderSlot是将this.$slots调用h转变为vnode

问题解决:

  1. 需要在绑定在this上面,那就在setupStatefulComponent函数代理中加入判断,传入的$slots ;
  2. 判断$slot是否在组件的代理中,然后代理需要把slots绑定在instance上面并且绑定值的时候需要把传入的对象统一转成数组;
  3. renderSlot方法调用了h函数,把一个数据转成vnode

编码实现

// 需要把$slots绑定在this上面,那就需要在代理里面在加入一个判断即可
function setupStatefulComponent(instance: any) {
  // 代理组件的上下文
  instance.proxy = new Proxy({  }, {
      get(target,key){
       // 省略其他
       else if(key in instance.slots){
         return instance.slots[key]
       }
      }
  })
}

// 接下里在instance上面加上slots属性
export function setupComponent(instance) {
  // 获取props和children
  const { props, children } = instance.vnode

  // 处理props
  
  const slots = {}
  for (const key in children) {
      slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
  }
  instance.slots = slots
  
  // ……省略其他
  }
  
  // 最后还需要使用renderSlot函数
  export function renderSlots(slots) {
    const slot = slots['default']
       if (slot) {
        return createVNode('div', {}, slot)
      }
}
复制代码

通过上面的编码,测试用例就可以完美通关啦

具名插槽

具名插槽就是,插槽除了可以有多个,并且除了default外,可以加入其他的名字,具体请看测试用例

测试用例

 test('测试具名插槽', () => {
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' },
          [
            renderSlots(this.$slots, 'header'),
            h('div', { class: 'default' }, 'default'),
            renderSlots(this.$slots, 'footer')
          ]
        );
      }
    }

    const app = createApp({
      name: 'App',
      render() {
        return h('div', { class: 'container' }, [h(Foo, {}, {
          header: h('h1', {}, 'header'),
          footer: h('p', {}, 'footer')
        })])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);

    const container = document.querySelector('.container') as HTMLElement

    expect(container.innerHTML).toBe('<div class="foo"><div><h1>header</h1></div><div class="default">default</div><div><p>footer</p></div></div>')
  })
复制代码

分析

通过上面测试用例,发现以下内容:

  1. renderSlot传入第二个参数,然后可以获取对于的slots

问题解决

直接在renderSlot里面传入第二个参数即可

编码

  // 最后还需要使用renderSlot函数
  export function renderSlots(slots, name = 'default') {
    const slot = slots[name]
       if (slot) {
        return createVNode('div', {}, slot)
      }
}
复制代码

这一步是不是比较简单,相对起前面来说,正所谓,前面考虑好了,后面就舒服,接下来实现作用域插槽

作用域插槽

作用域插槽是,每个slot里面可以传入数据,数据只在当前的slot有效,具体请看测试用例

测试用例

test('测试作用域插槽', () => {
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' },
          [
            renderSlots(this.$slots, 'header', { children: 'foo' }),
            h('div', { class: 'default' }, 'default'),
            renderSlots(this.$slots, 'footer')
          ]
        );
      }
    }

    const app = createApp({
      name: 'App',
      render() {
        return h('div', { class: 'container' }, [h(Foo, {}, {
          header: ({ children }) => h('h1', {}, 'header ' + children),
          footer: h('p', {}, 'footer')
        })])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);

    const container = document.querySelector('.container') as HTMLElement

    expect(container.innerHTML).toBe('<div class="foo"><div><h1>header foo</h1></div><div class="default">default</div><div><p>footer</p></div></div>')

  })
复制代码

需求分析

通过上面的测试用例,分析出以下内容:

  1. 传入插槽的时候,传入一个函数,函数可以拿到子组件传过来的参数
  2. renderSlots可以传入第三个参数props, 用于接收子组件往父组件传入的参数

问题解决:

  1. 问题1: 只需要在传入插槽的时候进行一下判断,如果是函数的话,需要进行函数执行,并且传入参数
  2. 问题2: 也是对传入的内容进行判断,函数做传入参数处理

编码

// 在renderSlot里面传入第三个参数

export function renderSlots(slots, name = 'default', props = {}) {
  const slot = slots[name];

  if (slot) {
    if (isFunction(slot)) {
      return createVNode('div', {}, slot(props))
    }
    return createVNode('div', {}, slot)
  }
}

// initSlot时候,需要进行函数判断

 const slots = {}
  // 遍历children
  for (const key in children) {
  // 判断传入的是否是函数,如果是函数的话,需要进行执行,并且传入参数
    if (isFunction(children[key])) {
      slots[key] = (props) => Array.isArray(children[key](props)) ? children[key](props) : [children[key](props)]
    } else {
      slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
    }
  }

  instance.slots = slots
复制代码

到此,整个测试用例就可以完美通过啦!



Tags:vue3   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: vue3  点击:(93)  评论:(0)  加入收藏
vue3中 ref和 reactive的区别 ?
最近有朋友在面试过程中经常被问到这么一个问题,vue3 中的ref 和 reactive的区别在哪里,为什么 要定义两个API 一个 api不能实现 响应式更新吗??带着这个疑问 ,我们 接下来进行逐...【详细内容】
2024-01-03  Search: vue3  点击:(47)  评论:(0)  加入收藏
React18 与 Vue3 全方面对比
1. 编程风格 & 视图风格1.1 编程风格 React 语法少、难度大;Vue 语法多,难度小例如指令:Vue<input v-model="username"/><ul> <li v-for="(item,index) in list" :key="inde...【详细内容】
2024-01-03  Search: vue3  点击:(78)  评论:(0)  加入收藏
Vue3 学习笔记,如何使用 Watch 监听数据变化
大家好,本篇文章我们继续学习和 Vue 相关的内容,今天我们归纳总结下如何使用 watch 监听组件中的数据变化,以及 computed 和 watch 的区别。什么是 watch,以及如何使用?watch 是...【详细内容】
2023-12-14  Search: vue3  点击:(175)  评论:(0)  加入收藏
Vue3 学习笔记,如何理解 Computed 计算属性
大家好,本篇文章我们继续学习和 Vue 相关的内容,今天我们归纳总结下什么是 computed 计算属性、如何使用和应用场景,以及 computed 和 Method 事件的区别和应用场景。什么是 co...【详细内容】
2023-12-11  Search: vue3  点击:(211)  评论:(0)  加入收藏
Vue3 学习笔记,如何定义事件以及如何理解响应式
如何定义事件在 Vue 中,可以使用 v-on 指令来绑定事件监听器。下面是一个示例,在点击按钮时触发事件处理程序:<template> <div> <button v-on:click="incrementCounter">{{...【详细内容】
2023-12-06  Search: vue3  点击:(141)  评论:(0)  加入收藏
最全Vue3开源管理系统汇总
搭建一个后台管理,从零开始开发,其实并不容易,在众多开源管理后台中,Vue3是一个备受瞩目的选择。它是一个现代化的前端框架,具有高效、灵活、易用等特点,今天,要为大家介绍几款开源...【详细内容】
2023-11-24  Search: vue3  点击:(219)  评论:(0)  加入收藏
Vue3问题:如何实现El-table内容超出省略提示?第三条很少有人会
一、需求分析,问题描述1、需求一个表格,分表头、表体、表尾三部分。当每个单元格的内容过长超出时,需要省略,用省略号代替超出的部分。同时,当鼠标移入上去时,会在上方弹出一个小...【详细内容】
2023-11-23  Search: vue3  点击:(98)  评论:(0)  加入收藏
Spring Boot + Vue3 前后端分离 实战wiki知识库系统
下栽の地止:https://www.itwangzi.cn/2508.html Spring Boot + Vue3 前后端分离 实战wiki知识库系统在当今的Web应用开发中,前后端分离已经成为了一种主流的开发模式。Spring...【详细内容】
2023-11-18  Search: vue3  点击:(147)  评论:(0)  加入收藏
深入详解Vue3中的Mock数据模拟
Vue3 前端开发时,需要api请求模拟和数据模拟,我将在接下来的时间写Mock模拟和json-server模拟。两种比较流行的模式,现在先介绍mock模式。使用NPM安装Mock.js库Mock.js是一个轻...【详细内容】
2023-11-07  Search: vue3  点击:(158)  评论:(0)  加入收藏
▌简易百科推荐
20k级别前端是怎么使用LocalStorage的,想知道吗?
当咱们把咱们想缓存的东西,存在localStorage、sessionStorage中,在开发过程中,确实有利于咱们的开发,咱们想看的时候也是一目了然,点击Application就可以看到。前言大家好,我是林...【详细内容】
2024-03-26  前端之神  微信公众号  Tags:前端   点击:(14)  评论:(0)  加入收藏
前端不存在了?盲测64%的人更喜欢GPT-4V的设计,杨笛一等团队新作
3 月 9 日央视的一档节目上,百度创始人、董事长兼 CEO 李彦宏指出,以后不会存在「程序员」这种职业了,因为只要会说话,人人都会具备程序员的能力。「未来的编程语言只会剩下两种...【详细内容】
2024-03-11  机器之心Pro    Tags:前端   点击:(17)  评论:(0)  加入收藏
前端开始“锈化”?Vue团队开源JS打包工具:基于Rust、速度极快、尤雨溪主导
Vue 团队已正式开源Rolldown &mdash;&mdash; 基于 Rust 的 JavaScrip 打包工具。Rolldown 是使用 Rust 开发的 Rollup 替代品,它提供与 Rollup 兼容的应用程序接口和插件接口...【详细内容】
2024-03-09  OSC开源社区    Tags:Vue   点击:(23)  评论:(0)  加入收藏
两年前端经验还不会手写Promise?
什么是promise?当我们处理异步操作时,我们经常需要进行一系列的操作,如请求数据、处理数据、渲染UI等。在过去,这些操作通常通过回调函数来处理,但是回调函数嵌套过多会导致代码...【详细内容】
2024-03-07  海燕技术栈  微信公众号  Tags:Promise   点击:(29)  评论:(0)  加入收藏
网站开发中的前端和后端开发有什么区别
前端开发和后端开发都是干什么的?有哪些区别?通俗地讲,前端干的工作是用户可以直接看得见的,而后端开发的工作主要在服务端,用户不太能直接看到。虽然前端开发和后端开发的工作有...【详细内容】
2024-02-21  CarryData    Tags:前端   点击:(37)  评论:(0)  加入收藏
网站程序开发中的前后端分离技术
随着互联网的快速发展和技术的不断创新,传统的网站开发模式已经难以满足日益增长的业务需求。为了提高开发效率、增强系统的可维护性和可扩展性,前后端分离技术逐渐成为了网站...【详细内容】
2024-01-31  网站建设派迪星航    Tags:前后端分离   点击:(27)  评论:(0)  加入收藏
如何优雅的实现前端国际化?
JavaScript 中每个常见问题都有许多成熟的解决方案。当然,国际化 (i18n) 也不例外,有很多成熟的 JavaScript i18n 库可供选择,下面就来分享一些热门的前端国际化库!i18nexti18ne...【详细内容】
2024-01-17  前端充电宝  微信公众号  Tags:前端   点击:(78)  评论:(0)  加入收藏
Vue中Scope是怎么做样式隔离的?
scope样式隔离在 Vue 中,样式隔离是通过 scoped 特性实现的。当在一个组件的 <style> 标签上添加 scoped 特性时,Vue 会自动为这个样式块中的所有选择器添加一个唯一的属性,以...【详细内容】
2024-01-04  海燕技术栈  微信公众号  Tags:Vue   点击:(85)  评论:(0)  加入收藏
vue3中 ref和 reactive的区别 ?
最近有朋友在面试过程中经常被问到这么一个问题,vue3 中的ref 和 reactive的区别在哪里,为什么 要定义两个API 一个 api不能实现 响应式更新吗??带着这个疑问 ,我们 接下来进行逐...【详细内容】
2024-01-03  互联网高级架构师  今日头条  Tags:vue3   点击:(47)  评论:(0)  加入收藏
React18 与 Vue3 全方面对比
1. 编程风格 & 视图风格1.1 编程风格 React 语法少、难度大;Vue 语法多,难度小例如指令:Vue<input v-model="username"/><ul> <li v-for="(item,index) in list" :key="inde...【详细内容】
2024-01-03  爱做梦的程序员  今日头条  Tags:Vue3   点击:(78)  评论:(0)  加入收藏
站内最新
站内热门
站内头条