vue渲染函数就是render函数,他会返回一个VNode,VNode是一个js对象,是dom的映射
vue在介绍渲染函数那个章节看的不是很懂,所以想要彻底的理解渲染函数,首先需要了解vue的虚拟dom
关于虚拟dom的介绍,这里有一篇很好的文章,我也是看了这篇才茅塞顿开:https://www.zhihu.com/question/29504639
我来概括一下虚拟dom的工作是将浏览器的dom节点的所有信息映射到一个js对象上面,因为js本身是很快的,但是dom操作本身很慢,如果我们把dom的信息映射到js对象上面,至少当获取dom信息时候 我们不需要去遍历dom了,而另一方面对于dom的操作一直是js优化的重点,使用虚拟dom,程序员不需要自己去操作dom,所以关于dom的操作都会由虚拟dom完成,而虚拟dom一定会以最优方案来进行操作,所以虚拟dom的重点是构建一个浏览器dom的信息拷贝树,拷贝树里面必须包含所有dom节点的拷贝节点,我们把这个拷贝节点叫做VNode
关于虚拟dom的好处还有一个就是vue会把所有的dom操作缓存到一个队列,这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。请看vue官方对于异步更新队列的解释:https://cn.vuejs.org/v2/guide/reactivity.html,
刚才我们说到VNode是什么,那VNode里面包含哪些信息呢:
tag
: 当前节点的标签名data
: 当前节点的数据对象,具体包含哪些字段可以参考vue源码types/vnode.d.ts
中对VNodeData
的定义children
: 数组类型,包含了当前节点的子节点text
: 当前节点的文本,一般文本节点或注释节点会有该属性elm
: 当前虚拟节点对应的真实的dom节点ns
: 节点的namespacecontext
: 编译作用域functionalContext
: 函数化组件的作用域key
: 节点的key属性,用于作为节点的标识,有利于patch的优化componentOptions
: 创建组件实例时会用到的选项信息child
: 当前节点对应的组件实例parent
: 组件的占位节点raw
: raw htmlisStatic
: 静态节点的标识isRootInsert
: 是否作为根节点插入,被<transition>
包裹的节点,该属性的值为false
isComment
: 当前节点是否是注释节点isCloned
: 当前节点是否为克隆节点isOnce
: 当前节点是否有v-once
指令
那如何创建VNode呢,首先为什么要创建VNode,因为我们使用vue所有的操作都是针对vue的虚拟dom也就是VNode的,然后vue将虚拟dom更新到真实dom,而我们经常使用的vue组建 也是在底层先变成虚拟dom然后再变成真实dom,拿好了 既然组建是先变成虚拟dom再变成VNode,那我们理论上可以自己创建VNode,然后让vue将我们创建的VNode更新到真实dom上,这样更* 权限更大,创建VNode的方法是createElement
createElement(tag,{},[]) 或者createElement(tag,{},string)
其中tag是创建元素的标签名
{}是创建元素的属性
[]是创建元素的子元素列表
string是文本
所以最终我们可以这样使用createElement:
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 标签名称 this.$slots.default // 子组件中的阵列 ) }, props: { level: { type: Number, required: true } } })
关于this.$slots.defalt:
slot我们知道是插槽,关于插槽我认为最好的解释是:在父组件中放入已被 `slot` 标记的内容,这些内容的顺序可以随意。之后这些内容被分发到子组件的特殊元素 `slot` 中,根据 `name` 属性在子组件中重新组合。this.$slots用来访问被插槽分发的内容,这一点vue解释的很清楚:https://cn.vuejs.org/v2/api/#vm-slots
这里使用一个例子来加深this.$slots.defalt的理解:
var getChildrenTextContent = function (children) { return children.map(function (node) { return node.children ? getChildrenTextContent(node.children) : node.text }).join('') } Vue.component('anchored-heading', { render: function (createElement) { // create kebabCase id var headingId = getChildrenTextContent(this.$slots.default) .toLowerCase() .replace(/\W+/g, '-') .replace(/(^\-|\-$)/g, '') return createElement( 'h' + this.level, [ createElement('a', { attrs: { name: headingId, href: '#' + headingId } }, this.$slots.default) ] ) }, props: { level: { type: Number, required: true } } })
这里我们首先getChildrenTextContent,getChildrenTextContent方法的参数是一个数组,说明this.$slot.defaul是一个数组,他其实就是[VNode,Vnode..],this.$slot.default数组里每一个都是VNode对象,上文我们提到到VNode有哪些属性,所以我们看到getChildrenTextContent方法里去判断VNode的children属性,如果VNode有子元素就继续向下寻找,如果没有则直接取他的文本,所以最终我们拿到的是准备放在slot位置的所有非具名元素的文本的拼接。