「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗

大家好我是林三心,Vue 实现了一套内容分发的 API,将 元素作为承载分发内容的出口,这是Vue文档上的说明。具体来说, slot就是可以让你在组件内添加内容的‘空间’,你真的知道插槽Slot是怎么“插”的吗?我希望你们能像我一样单纯,老老实实地看这篇文章。

「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗_第1张图片

Vue插槽slot的基本使用

单个插槽 | 匿名插槽

//子组件 : (假设名为:child)


//父组件:(引用子组件 child)
我们知道,如果直接在父组件中的 中添加内容“林三心”,“林三心”文本是不会在页面上渲染的。那么我们如何使添加的内容能够显示呢?在子组件内添加slot 即可。
//子组件 : (假设名为:child)

编译作用域 (父组件在子组件处插入 data)

上面我们了解了,slot 其实就是能够让我们在父组件中添加内容到子组件的‘空间’。我们可以添加父组件内任意的data值,比如这样:
//父组件:(引用子组件 child)


new Vue({
  el:'.app',
  data:{
    parent:'父组件'
  }
})
使用数据的语法完全没有变,但是,我们能否直接使用子组件内的数据呢?显然不行!!
// 子组件 : (假设名为:child)


new Vue({
  el:'child',
  data:{
    child:'子组件'
  }
})

// 父组件:(引用子组件 child)

直接传入子组件内的数据是不可以的。因为:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

后备内容 (子组件设置默认值)

所谓的后背内容,其实就是slot的默认值,有时我没有在父组件内添加内容,那么 slot就会显示默认值,如:
//子组件 : (假设名为:child)

具名插槽 (子组件多个对应插入内容)

有时候,也许子组件内的slot不止一个,那么我们如何在父组件中,精确的在想要的位置,插入对应的内容呢? 给插槽命一个名即可,即添加name属性。
//子组件 : (假设名为:child)
父组件通过,或 slot="name"(旧语法)v-slot:name#name(新语法) 的方式添加内容:
//父组件:(引用子组件 child)

作用域插槽 (父组件在子组件处使用子组件 data)

通过 slot 我们可以在父组件为子组件添加内容,通过给 slot命名的方式,我们可以添加不止一个位置的内容。但是我们添加的数据都是父组件内的。上面我们说过不能直接使用子组件内的数据,但是我们是否有其他的方法,让我们能够使用子组件的数据呢? 其实我们也可以使用 slot-scope的方式:
//子组件 : (假设名为:child)


new Vue({
  el:'child',
  data:{
    child1:'数据1',
    child2:'数据2'
  }
})

//父组件:(引用子组件 child)

Slot插槽是怎么“插”的(通俗版)

普通插槽

//子组件 : (假设名为:child)


//父组件:(引用子组件 child)


new Vue({
  el:'.app',
  data:{
    parent:'父组件的值'
  }
})
  1. 父组件先解析,把 child 当做子元素处理,把 插槽当做 child 的子元素处理,并且在父组件作用域内得出了parent变量的值,生成这样的节点:

    {    
      tag: "div",    
    children: [{        
       tag: "child",        
       children: ['这是插入到默认插槽的内容 父组件的值', 
                   '这是插入到one插槽的内容 父组件的值']
      }]
    }
  2. 子组件解析,slot 作为一个占位符,会被解析成一个函数,大概意思像 解析成下面

    {    
     tag: "div",    
     children: [
         '我在子组件里面',
         _t('default'), // 匿名插槽,默认名称为default
         _t('one') // 具名插槽,名称为one
     ]
    }
  3. _t函数需要传入插槽名称,默认是default,具名插槽则传入name,这个函数的作用,是把第一步解析得到的插槽节点拿到,然后返回解析后的节点,那么子组件的节点就完整了,插槽也成功认了爹——div标签

    {    
     tag: "div",    
     children: ['我在子组件里面', 
                 '这是插入到默认插槽的内容 父组件的值', 
                 '这是插入到one插槽的内容 父组件的值']
    }

作用域插槽

//子组件 : (假设名为:child)


new Vue({
  el:'child',
  data:{
    child1: '子数据1',
    child2: '子数据2'
  }
})

//父组件:(引用子组件 child)
  1. 过程很复杂,这里就通俗点讲了,父组件先解析,遇到作用域插槽,会将此插槽封装成一个函数保存到子元素 child

    {    
     tag: "div",    
      children: [{        
      tag: "child"
      scopeSlots:{            
          default (data) { // 记住这个data参数               
              return ['插入one slot 中插入默认 slot 中' + data.value1 + data.value2]
          },
          one (data) { // 记住这个data参数             
              return ['插入one slot 中' + data.value1 + data.value2]
          }
      }
     }]
    }

2.轮到子组件解析了,这个时候_t函数又登场了,并且子组件将对应的插槽数据包装成一个对象,传进_t函数

{    
 tag: "div",    
   children: [
     '我在子组件里面',
      _t('default',{value1: '子数据1', value2: '子数据1'}),
      _t('one',{value1: '子数据2', value2: '子数据2'})
      
    ]
  }
接下来就是_t内部执行,包装后的对象被当做data参数传入了scopeSlots中的对应的各个函数,解析成:
{    
  tag: "div",    
   children: [
      '我在子组件里面', 
      '插入默认 slot 中 子数据1 子数据1',
      '插入one slot 中 子数据2 子数据2'
   ]
}

$slots

看到这,相信大家已经清楚了大概一个过程(虽然不是很详细),那么又有一个疑问了,这些解析后的 节点VNode对象,是存在哪儿呢?你总不能解析出来然后就扔了吧?肯定得找个地方存起来然后进行 真实dom的渲染,这个地方就是 $slots
//子组件 : (假设名为:child)


new Vue({
  el:'.child',
  created () {
      console.log(this.$slots) // 看看里面有啥
  }
})
//父组件:(引用子组件 child)
console.log的结果:

「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗_第2张图片

看到这里大家都懂了, $slots是一个 Mapkey是各个插槽的名称(匿名插槽的 keydefault),key对应的value都是各个插槽下面的VNode节点,具体VNode对象里都是长什么样,大家可以自己输出看看,里面东西太多,我就不在这展示了。嘿嘿。

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

image.png

你可能感兴趣的:(前端vue.js源码面试es6)