We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
插槽看上去很高级很复杂,其实实现起来并不复杂!
插槽的真正内容是在父组件上的,所以创建子组件之前,子组件里面的数据对应的vnode已经存在了。这里使用,我们把它归类到实例的 $slots 对象上,它是一个数组的对象。
$slots
然后再子组件渲染的时候,再把他的数据(就是vnode)拿出来,当到子组件的child里面,snabbdom自动就会渲染出来了!
再创建子组件的时候,增加代码。把 父组件 里面的vnode根据插槽归类。
父组件
/** * 实现组件功能 * * 采用snabbdom的hook,在insert和update的时候更新数据。 * * @param {*} vnode * @param {*} vm */ function setComponentHook(vnode: any, vm: Xiao) { if (!vnode.sel) { return } // 查看是否组成了组件? const Comp = Xiao.component(vnode.sel) if (Comp) { vnode.data.hook = { insert: (vnode) => { log('component vnode', vnode) // 创建子组件实例 let app = new Comp() app.$parent = vm const propsData = vnode.data.props // 把计算后的props数据代理到当前vue里面 initProps(app, propsData) // 处理插槽,把插槽归类 resolveSlots(app, vnode.children) // 绑定事件 if(vnode.data.on){ initEvent(app, vnode.data.on) } // 保存到vnode中,更新的时候需要取出来用 vnode.childContext = app // 渲染 app.$mount(vnode.elm) }, update: (oldvnode, vnode) => { const app = oldvnode.childContext // 更新update属性 updateProps(app, vnode.data.props) vnode.childContext = app } } } // 递归 if (vnode.children) { vnode.children.forEach(function (e) { setComponentHook(e, vm) }, this) } } /** * 归类插槽 * * @param {*} vm * @param {*} children */ function resolveSlots(vm: Xiao, children: Array<any>){ log('resolveSlots', children) vm.$slots = {} children.forEach(vnode =>{ let slotname = 'default' if(vnode.data.props && vnode.data.props.slot){ slotname = vnode.data.props.slot delete vnode.data.props.slot } (vm.$slots[slotname] || (vm.$slots[slotname] = [])).push(vnode) }) log('resolveSlots end', vm.$slots) }
/** * 根据元素AST生成渲染函数。 * * 如果是插槽,生成 _t(插槽名字, [默认插槽内容]) * 否则生成 h(tag, 属性。。。) * * @param {*} node */ function createRenderStrElemnet(node: any): string { log('createRenderStrElemnet', node) let str: string // 插槽使用 _t 函数, 参数为插槽名字 if (node.tag == 'slot') { log('slot node', node) const slot = node.attrsMap.name || "default" str = `_t("${slot}",[` if (node.children && node.children.length > 0) { // 生成插槽默认的子组件的渲染函数 for (let i = 0; i < node.children.length; i++) { str += createRenderStr(node.children[i]) if (i != node.children.length - 1) { str += ',' } } } str += '])' return str } // snabbdom 的语法,类名放在tag上。'div#container.two.classes' let tagWithIdClass = getTagAndClassName(node) str = `h(${tagWithIdClass},{` // 解析指令 str += getDirectiveStr(node) // 解析属性 str += genAttrStr(node) str += "}" if (node.children) { str += ',[' // 保存上一次if指令,处理只有if没有else的场景 let lastDir node.children.forEach(child => { // 如果这里节点有if指令 let dir = getIfElseDirective(child) console.log('dir:', dir) if (dir) { if (dir.name == 'if') { str += `(${dir.exp})?` lastDir = dir } else if (dir.name == 'else') { str += `:` } } str += createRenderStr(child) if (dir) { if (dir.name == 'else') { str += `,` lastDir = null } } else if (lastDir) { str += `:"",` lastDir = null } else { str += `,` } }) if (lastDir) { str += `:"",` } str += ']' } str += ')' return str }
class Xiao{ /** * 插槽渲染函数 * * vue里面是 _t = renderSlot * @param {*} slot */ _t(slot: string, child: ?any){ // 如果父节点没有制定插槽内容,那么返回默认值(是个数组) return this.$slots[slot] || child } }
渲染函数执行之后,插槽渲染函数 _t 执行返回vnode节点数组。把他打散到原来的数组里面即可。
插槽渲染函数 _t
/** * 渲染组件 * * @param {*} vm */ function updateComponent(vm: Xiao) { let proxy = vm // 虚拟dom里面的创建函数 proxy.h = h // 新的虚拟节点 // 指令的信息已经自动附带再vnode里面 let vnode = vm.$render.call(proxy, h) log('before expandSlotArray: ', vnode) // 插槽后child里面应该为节点的可能变成了数组,所以要单独处理一下 expandSlotArray(vnode) log('after expandSlotArray: ', vnode) // 把实例绑定到vnode中,处理指令需要用到 setContext(vnode, vm) // 处理子组件 setComponentHook(vnode, vm) // 上一次渲染的虚拟dom let preNode = vm.$options.oldvnode; log(`[lifecycle][uid:${vm._uid}] 第${++vm._renderCount}次渲染`) if (preNode) { vnode = patch(preNode, vnode) } else { vnode = patch(vm.$el, vnode) } log('vnode', vnode) // 保存起来,下次patch需要用到 vm.$options.oldvnode = vnode; }
<h1>slot测试</h1> <div id="demo"> <h1>我是父组件的标题</h1> <my-component> <p>这是一些初始内容{{message}}</p> <p>这是更多的初始内容</p> <h3 slot="header">这是父组件放到子组件头插槽的数据</h3> <h2 slot="foot2">放到foot2插槽{{message}}</h2> </my-component> </div> <script> Xiao.component('my-component', { data: function(){ return { aa: 'child message' } }, template: ` <div> <slot name="header"></slot> <h2>我是子组件的标题{{aa}}</h2> <slot> 只有在没有要分发的内容时才会显示。 </slot> <slot name="foot"><h2>默认的foot slot:{{aa}}</h2></slot> <slot name="foot2">默认的foot2 slot:{{aa}}</slot> </div> ` }) var app = new Xiao({ el: '#demo', data:{ message : 'parent message' } }) </script>
The text was updated successfully, but these errors were encountered:
No branches or pull requests
实现思路
插槽看上去很高级很复杂,其实实现起来并不复杂!
插槽的真正内容是在父组件上的,所以创建子组件之前,子组件里面的数据对应的vnode已经存在了。这里使用,我们把它归类到实例的
$slots
对象上,它是一个数组的对象。然后再子组件渲染的时候,再把他的数据(就是vnode)拿出来,当到子组件的child里面,snabbdom自动就会渲染出来了!
归类子组件到$slots
再创建子组件的时候,增加代码。把
父组件
里面的vnode根据插槽归类。修改渲染函数
实现插槽渲染函数
更新组件的时候,把父组件的数据填充到子元素上
渲染函数执行之后,
插槽渲染函数 _t
执行返回vnode节点数组。把他打散到原来的数组里面即可。测试代码
The text was updated successfully, but these errors were encountered: