이번에는 Vue에서 슬롯/범위 사용에 대해 자세히 설명하겠습니다. Vue에서 슬롯/범위 사용 시 주의사항은 무엇인가요?
다음은 슬롯의 작동 원리를 간략하게 설명하는 예입니다
dx-li 하위 구성 요소의 템플릿은 다음과 같습니다.
<li class="dx-li"> <slot> 你好! </slot> </li> dx-ul父组件的template如下: <ul> <dx-li> hello juejin! </dx-li> </ul> 结合上述例子以及vue中相关源码进行分析 dx-ul父组件中template编译后,生成的组件render函数: module.exports={ render:function (){ var _vm=this; var _h=_vm.$createElement; var _c=_vm._self._c||_h; // 其中_vm.v为createTextVNode创建文本VNode的函数 return _c('ul', [_c('dx-li', [_vm._v("hello juejin!")])], 1) }, staticRenderFns: [] }
전달된 슬롯 콘텐츠 'hello juejin!' dx-li 하위 구성 요소 VNode 노드의 하위 노드입니다.
dx-li 하위 구성 요소를 렌더링합니다. 여기서 하위 구성 요소의 렌더링 기능은
module.exports={ render:function (){ var _vm=this; var _h=_vm.$createElement; var _c=_vm._self._c||_h; // 其中_vm._v 函数为renderSlot函数 return _c('li', {staticClass: "dx-li" }, [_vm._t("default", [_vm._v("你好 掘金!")])], 2 ) }, staticRenderFns: [] }
dx-li 하위 구성 요소 vue 인스턴스를 초기화하는 동안 initRender 함수가 호출됩니다.
function initRender (vm) { ... // 其中_renderChildren数组,存储为 'hello juejin!'的VNode节点;renderContext一般为父组件Vue实例 这里为dx-ul组件实例 vm.$slots = resolveSlots(options._renderChildren, renderContext); ... }
resolveSlots 함수는
/** * 主要作用是将children VNodes转化成一个slots对象. */ export function resolveSlots ( children: ?Array<VNode>, context: ?Component ): { [key: string]: Array<VNode> } { const slots = {} // 判断是否有children,即是否有插槽VNode if (!children) { return slots } // 遍历父组件节点的孩子节点 for (let i = 0, l = children.length; i < l; i++) { const child = children[i] // data为VNodeData,保存父组件传递到子组件的props以及attrs等 const data = child.data /* 移除slot属性 * <span slot="abc"></span> * 编译成span的VNode节点data = {attrs:{slot: "abc"}, slot: "abc"},所以这里删除该节点attrs的slot */ if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot } /* 判断是否为具名插槽,如果为具名插槽,还需要子组件/函数子组件渲染上下文一致。主要作用: *当需要向子组件的子组件传递具名插槽时,不会保持插槽的名字。 * 举个栗子: * child组件template: * <p> * <p class="default"><slot></slot></p> * <p class="named"><slot name="foo"></slot></p> * </p> * parent组件template: * <child><slot name="foo"></slot></child> * main组件template: * <parent><span slot="foo">foo</span></parent> * 此时main渲染的结果: * <p> * <p class="default"><span slot="foo">foo</span></p> <p class="named"></p> * </p> */ if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { const name = data.slot const slot = (slots[name] || (slots[name] = [])) // 这里处理父组件采用template形式的插槽 if (child.tag === 'template') { slot.push.apply(slot, child.children || []) } else { slot.push(child) } } else { // 返回匿名default插槽VNode数组 (slots.default || (slots.default = [])).push(child) } } // 忽略仅仅包含whitespace的插槽 for (const name in slots) { if (slots[name].every(isWhitespace)) { delete slots[name] } } return slots }
그런 다음 dx -li 구성 요소를 마운트하면 dx-li 구성 요소 렌더링 함수가 호출되고 이 프로세스 중에 renderSlot 함수가 호출됩니다.
export function renderSlot ( name: string, // 子组件中slot的name,匿名default fallback: ?Array<VNode>, // 子组件插槽中默认内容VNode数组,如果没有插槽内容,则显示该内容 props: ?Object, // 子组件传递到插槽的props bindObject: ?Object // 针对<slot v-bind="obj"></slot> obj必须是一个对象 ): ?Array<VNode> { // 判断父组件是否传递作用域插槽 const scopedSlotFn = this.$scopedSlots[name] let nodes if (scopedSlotFn) { // scoped slot props = props || {} if (bindObject) { if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { warn( 'slot v-bind without argument expects an Object', this ) } props = extend(extend({}, bindObject), props) } // 传入props生成相应的VNode nodes = scopedSlotFn(props) || fallback } else { // 如果父组件没有传递作用域插槽 const slotNodes = this.$slots[name] // warn duplicate slot usage if (slotNodes) { if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) { warn( `Duplicate presence of slot "${name}" found in the same render tree ` + `- this will likely cause render errors.`, this ) } // 设置父组件传递插槽的VNode._rendered,用于后面判断是否有重名slot slotNodes._rendered = true } // 如果没有传入插槽,则为默认插槽内容VNode nodes = slotNodes || fallback } // 如果还需要向子组件的子组件传递slot /*举个栗子: * Bar组件: <p class="bar"><slot name="foo"/></p> * Foo组件:<p class="foo"><bar><slot slot="foo"/></bar></p> * main组件:<p><foo>hello</foo></p> * 最终渲染:<p class="foo"><p class="bar">hello</p></p> */ const target = props && props.slot if (target) { return this.$createElement('template', { slot: target }, nodes) } else { return nodes } }
범위가 지정된 슬롯은 다음과 같이
dx-li 하위 구성 요소의 템플릿을 이해합니다.
<li class="dx-li"> <slot str="你好 掘金!"> hello juejin! </slot> </li> dx-ul父组件的template如下: <ul> <dx-li> <span slot-scope="scope"> {{scope.str}} </span> </dx-li> </ul> 结合例子和Vue源码简单作用域插槽 dx-ul父组件中template编译后,产生组件render函数: module.exports={ render:function (){ var _vm=this; var _h=_vm.$createElement; var _c=_vm._self._c||_h; return _c('ul', [_c('dx-li', { // 可以编译生成一个对象数组 scopedSlots: _vm._u([{ key: "default", fn: function(scope) { return _c('span', {}, [_vm._v(_vm._s(scope.str))] ) } }]) })], 1) }, staticRenderFns: [] }
where _vm._u 함수:
function resolveScopedSlots ( fns, // 为一个对象数组,见上文scopedSlots res ) { res = res || {}; for (var i = 0; i < fns.length; i++) { if (Array.isArray(fns[i])) { // 递归调用 resolveScopedSlots(fns[i], res); } else { res[fns[i].key] = fns[i].fn; } } return res }
하위 구성 요소의 후속 렌더링 프로세스는 슬롯과 유사합니다. 범위가 지정된 슬롯의 원리는 기본적으로 슬롯의 원리와 동일합니다. 차이점은 상위 구성 요소 템플릿을 컴파일할 때 VNode를 반환하는 함수가 생성된다는 것입니다. 하위 구성 요소가 상위 구성 요소가 전달한 범위 슬롯 함수와 일치하면 해당 VNode를 생성하기 위해 함수가 호출됩니다.
요약
사실 슬롯/범위 슬롯의 원리는 매우 간단합니다. vue가 구성 요소를 렌더링할 때 VNode를 기반으로 실제 DOM 요소를 렌더링한다는 점만 이해하면 됩니다.
슬롯은 상위 구성 요소를 컴파일하여 생성된 슬롯 VNode입니다. 하위 구성 요소를 렌더링할 때 해당 하위 구성 요소 렌더링 VNode 트리에 배치됩니다.
범위 슬롯은 상위 구성 요소의 슬롯 콘텐츠를 함수로 컴파일합니다. 하위 구성 요소를 렌더링할 때 하위 구성 요소 props를 전달하고 해당 VNode를 생성합니다. 마지막으로 하위 구성 요소는 구성 요소 렌더링 기능에 따라 VNode 노드 트리를 반환하고 업데이트하여 실제 DOM 요소를 렌더링합니다. 동시에 구성 요소 간에 슬롯을 전달하는 것도 가능하지만 명명된 슬롯을 전달하려면 주의가 필요하다는 것을 알 수 있습니다.
이 기사의 사례를 읽은 후 방법을 마스터했다고 생각합니다. 더 흥미로운 정보를 보려면 PHP 중국어 웹사이트의 다른 관련 기사를 주목하세요!
추천 도서:
vue 구성 요소에서 슬롯 소켓 사용에 대한 자세한 설명
위 내용은 Vue에서 슬롯/범위 사용에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!