前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >什么是作用域插槽?插槽与作用域插槽的区别

什么是作用域插槽?插槽与作用域插槽的区别

作者头像
全栈程序员站长
发布2022-09-20 10:32:12
9390
发布2022-09-20 10:32:12
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君

一、概念

  1. // 有name的属于具名插槽,没有name属于匿名插槽
  2. <app>
  3. <div slot=“a”>xxxx</div>
  4. <div slot=“b”>xxxx</div>
  5. </app>
  6. <slot name=“a”></slot>
  7. <slot name=“b”></slot>
  8. 普通插槽渲染的位置是在它的父组件里面,而不是在子组件里面
  9. 作用域插槽渲染是在子组件里面

1.插槽slot

在渲染父组件的时候,会将插槽中的先渲染。

创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿 子进行分类 {a:[vnode],b[vnode]}

渲染组件时会拿对应的slot属性的节点进行替换操作。(插槽的作用域为父组件,插槽中HTML模板显示不显示、以及怎样显示由父组件来决定)

有name的父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。

2.作用域插槽slot-scope

作用域插槽在解析的时候,不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。

或者可以说成作用域插槽是子组件可以在slot标签上绑定属性值,在父组件可以拿到子组件的数据,通过子组件绑定数据传递给父组件。(插槽的作用域为子组件)

  1. 子组件:
  2. <slot :nickName= “‘wthreesix'”></slot>
  3. 父组件:
  4. <slot-child>
  5. <template slot-scope=“scope”>
  6. <div>{{scope.nickName}}</div>
  7. </template>
  8. </slot-child>

二、源码图

什么是作用域插槽?插槽与作用域插槽的区别
什么是作用域插槽?插槽与作用域插槽的区别

三、插槽渲染分析

1.插槽

  1. // 父组件:
  2. const VueTemplateCompiler = require(‘vue-template-compiler’);
  3. let ele = VueTemplateCompiler.compile(`
  4. <my-component>
  5. <div slot=”header”>node</div>
  6. <div>react</div>
  7. <div slot=”footer”>vue</div>
  8. </my-component>
  9. `)
  10. 将上面组件编译后:
  11. /**
  12. with(this) {
  13. return _c(‘my-component’, // _c 创建虚拟dom
  14. [ // 组件中包含子节点
  15. _c(‘div’, // 第一个是一个插槽,插槽叫header
  16. {
  17. attrs: {
  18. “slot”: “header”
  19. },
  20. slot: “header”
  21. },
  22. [_v(“node”)] // 将里面内容存起来了(_v是createTextVNode创建文本节点)
  23. ),
  24. _v(” “),
  25. _c(‘div’,[_v(“react”)]),
  26. _v(” “),
  27. _c(‘div’, {
  28. attrs: {
  29. “slot”: “footer”
  30. },
  31. slot: “footer”
  32. }, [_v(“vue”)])
  33. ]
  34. )
  35. }
  36. */
  37. 在调render方法的时候已经将组件全部渲染好了
  38. // 子组件
  39. let ele = VueTemplateCompiler.compile(`
  40. <div>
  41. <slot name=”header”></slot>
  42. <slot name=”footer”></slot>
  43. <slot></slot>
  44. </div>
  45. `);
  46. /**
  47. with(this) {
  48. // 渲染的时候会找header对应的是谁
  49. return _c(‘div’, [
  50. _t(“header”), _v(” “),
  51. _t(“footer”), _v(” “),
  52. _t(“default”)], 2)
  53. }
  54. 当找到就会换过来,如下:
  55. return _c(‘div’, [_v(“node”), _v(” “), _v(“vue”), _v(” “),
  56. _t(“default”)], 2)
  57. }
  58. }
  59. **/
  60. // _t是renderSlot
  61. 插槽就是一个替换的过程,将父组件渲染好的结果直接替换到自己的上面,创建的过程相当于在父组件渲染的

2.作用域插槽

  1. 父组件:
  2. let ele = VueTemplateCompiler.compile(`
  3. <app>
  4. <div slot-scope=”msg” slot=”footer”>{{msg.a}}</div>
  5. </app>
  6. `);
  7. /**
  8. with(this) {
  9. // 编译出的不是一个child,而是一个属性scopedSlots
  10. return _c(‘app’, {
  11. scopedSlots: _u([{
  12. key: “footer”,
  13. fn: function (msg) { 将子节点变成一个函数,这个函数不调用就不会去渲染这个子节点
  14. return _c(‘div’, {}, [_v(_s(msg.a))])
  15. }
  16. }])
  17. })
  18. }
  19. }
  20. 在初始化的时候并不会渲染子节点
  21. */
  22. 子组件:
  23. const VueTemplateCompiler = require(‘vue-template-compiler’);
  24. VueTemplateCompiler.compile( `
  25. // 当我们在写slot去执行的时候进行渲染
  26. <div>
  27. <slot name=”footer” a=”1″ b=”2″></slot>
  28. </div>
  29. `);
  30. /**
  31. with(this) {
  32. // 去找footer,找见会调用上面的那个函数,并且将属性传入到这个函数里面,这时候才会把这节点进行渲染,完成之后替换调
  33. return _c(‘div’, [_t(“footer”, null, {
  34. “a”: “1”,
  35. “b”: “2”
  36. })], 2)
  37. }
  38. **/
  39. // 作用域插槽的内容会被渲染成一个函数
  40. // 作用域插槽渲染是在当前组件的内部,不是在父组件中

四、源码

1.initRender(初始化render,构建vm.$slots)

  1. export function initRender (vm: Component) {
  2. vm.$slots = resolveSlots(options._renderChildren, renderContext)
  3. vm.$scopedSlots = emptyObject
  4. }

2.resolveSlots(映射slot名字和对应的vnode)

  1. export function resolveSlots (
  2. children: ?Array<VNode>,
  3. context: ?Component
  4. ): { [key: string]: Array<VNode> } {
  5. if (!children || !children.length) {
  6. return {}
  7. }
  8. const slots = {}
  9. for (let i = 0, l = children.length; i < l; i++) {
  10. const child = children[i]
  11. const data = child.data
  12. // remove slot attribute if the node is resolved as a Vue slot node
  13. if (data && data.attrs && data.attrs.slot) {
  14. delete data.attrs.slot
  15. }
  16. // named slots should only be respected if the vnode was rendered in the
  17. // same context.
  18. if ((child.context === context || child.fnContext === context) &&
  19. data && data.slot != null
  20. ) {
  21. const name = data.slot
  22. const slot = (slots[name] || (slots[name] = []))
  23. if (child.tag === ‘template’) {
  24. slot.push.apply(slot, child.children || [])
  25. } else {
  26. slot.push(child)
  27. }
  28. } else {
  29. (slots.default || (slots.default = [])).push(child)
  30. }
  31. }
  32. // ignore slots that contains only whitespace
  33. for (const name in slots) {
  34. if (slots[name].every(isWhitespace)) {
  35. delete slots[name]
  36. }
  37. }
  38. return slots
  39. }

3.normalizeScopedSlots(core/vdom/helpers/normalize-scoped-slot.js)

  1. export function normalizeScopedSlots (
  2. slots: { [key: string]: Function } | void,
  3. normalSlots: { [key: string]: Array<VNode> },
  4. prevSlots?: { [key: string]: Function } | void
  5. ): any {
  6. let res
  7. const hasNormalSlots = Object.keys(normalSlots).length > 0
  8. const isStable = slots ? !!slots.$stable : !hasNormalSlots
  9. const key = slots && slots.$key
  10. if (!slots) {
  11. res = {}
  12. } else if (slots._normalized) {
  13. // fast path 1: child component re-render only, parent did not change
  14. return slots._normalized
  15. } else if (
  16. isStable &&
  17. prevSlots &&
  18. prevSlots !== emptyObject &&
  19. key === prevSlots.$key &&
  20. !hasNormalSlots &&
  21. !prevSlots.$hasNormal
  22. ) {
  23. // fast path 2: stable scoped slots w/ no normal slots to proxy,
  24. // only need to normalize once
  25. return prevSlots
  26. } else {
  27. res = {}
  28. for (const key in slots) {
  29. if (slots[key] && key[0] !== ‘$’) {
  30. res[key] = normalizeScopedSlot(normalSlots, key, slots[key]) // 作用域插槽
  31. }
  32. }
  33. }
  34. // expose normal slots on scopedSlots
  35. for (const key in normalSlots) {
  36. if (!(key in res)) {
  37. res[key] = proxyNormalSlot(normalSlots, key) // 普通插槽
  38. }
  39. }
  40. // avoriaz seems to mock a non-extensible $scopedSlots object
  41. // and when that is passed down this would cause an error
  42. if (slots && Object.isExtensible(slots)) {
  43. (slots: any)._normalized = res
  44. }
  45. def(res, ‘$stable’, isStable)
  46. def(res, ‘$key’, key)
  47. def(res, ‘$hasNormal’, hasNormalSlots)
  48. return res
  49. }

4.proxyNormalSlot(将slot代理到scopeSlots上)

  1. function proxyNormalSlot(slots, key) {
  2. return () => slots[key]
  3. }

5.normalizeScopedSlot(将scopeSlots对应属性和方法挂载到scopeSlots)

  1. function normalizeScopedSlot(normalSlots, key, fn) {
  2. const normalized = function () {
  3. let res = arguments.length ? fn.apply(null, arguments) : fn({})
  4. res = res && typeof res === ‘object’ && !Array.isArray(res)
  5. ? [res] // single vnode
  6. : normalizeChildren(res)
  7. return res && (
  8. res.length === 0 ||
  9. (res.length === 1 && res[0].isComment) // #9658
  10. ) ? undefined
  11. : res
  12. }
  13. // this is a slot using the new v-slot syntax without scope. although it is
  14. // compiled as a scoped slot, render fn users would expect it to be present
  15. // on this.$slots because the usage is semantically a normal slot.
  16. if (fn.proxy) {
  17. Object.defineProperty(normalSlots, key, {
  18. get: normalized,
  19. enumerable: true,
  20. configurable: true
  21. })
  22. }
  23. return normalized
  24. }

6.最后调用renderSlot用函数的返回值进行渲染。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/168374.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概念
  • 二、源码图
  • 三、插槽渲染分析
  • 四、源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档