百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

vue3源码分析——实现slots vue3.0 slot

ztj100 2024-12-22 22:03 15 浏览 0 评论


引言

<<往期回顾>>

  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
复制代码

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

相关推荐

如何将数据仓库迁移到阿里云 AnalyticDB for PostgreSQL

阿里云AnalyticDBforPostgreSQL(以下简称ADBPG,即原HybridDBforPostgreSQL)为基于PostgreSQL内核的MPP架构的实时数据仓库服务,可以...

Python数据分析:探索性分析

写在前面如果你忘记了前面的文章,可以看看加深印象:Python数据处理...

CSP-J/S冲奖第21天:插入排序

...

C++基础语法梳理:算法丨十大排序算法(二)

本期是C++基础语法分享的第十六节,今天给大家来梳理一下十大排序算法后五个!归并排序...

C 语言的标准库有哪些

C语言的标准库并不是一个单一的实体,而是由一系列头文件(headerfiles)组成的集合。每个头文件声明了一组相关的函数、宏、类型和常量。程序员通过在代码中使用#include<...

[深度学习] ncnn安装和调用基础教程

1介绍ncnn是腾讯开发的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台,但是通常都需要protobuf和opencv。ncnn目前已在腾讯多款应用中使用,如QQ,Qzon...

用rust实现经典的冒泡排序和快速排序

1.假设待排序数组如下letmutarr=[5,3,8,4,2,7,1];...

ncnn+PPYOLOv2首次结合!全网最详细代码解读来了

编辑:好困LRS【新智元导读】今天给大家安利一个宝藏仓库miemiedetection,该仓库集合了PPYOLO、PPYOLOv2、PPYOLOE三个算法pytorch实现三合一,其中的PPYOL...

C++特性使用建议

1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...

Qt4/5升级到Qt6吐血经验总结V202308

00:直观总结增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理。把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能。把一些Qt5中兼容Qt4的方法废...

到底什么是C++11新特性,请看下文

C++11是一个比较大的更新,引入了很多新特性,以下是对这些特性的详细解释,帮助您快速理解C++11的内容1.自动类型推导(auto和decltype)...

掌握C++11这些特性,代码简洁性、安全性和性能轻松跃升!

C++11(又称C++0x)是C++编程语言的一次重大更新,引入了许多新特性,显著提升了代码简洁性、安全性和性能。以下是主要特性的分类介绍及示例:一、核心语言特性1.自动类型推导(auto)编译器自...

经典算法——凸包算法

凸包算法(ConvexHull)一、概念与问题描述凸包是指在平面上给定一组点,找到包含这些点的最小面积或最小周长的凸多边形。这个多边形没有任何内凹部分,即从一个多边形内的任意一点画一条线到多边形边界...

一起学习c++11——c++11中的新增的容器

c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...

C++ 编程中的一些最佳实践

1.遵循代码简洁原则尽量避免冗余代码,通过模块化设计、清晰的命名和良好的结构,让代码更易于阅读和维护...

取消回复欢迎 发表评论: