h()
看到这个函数你可能会有些许困惑,为什么叫h呢?代表着什么呢?
h 其实代表的是 hyperscript 。它是 HTML 的一部分,表示的是超文本标记语言,当我们正在处理一个脚本的时候,在虚拟 DOM 节点中去使用它进行替换已成为一种惯例。这个定义同时也被运用到其他的框架文档中
Hyperscript 它本身表示的是 “生成描述 HTML 结构的脚本”
好了,了解了什么是 h,现在我们来看官方对他的一个定义
定义: 返回一个“虚拟节点” ,通常缩写为 VNode: 一个普通对象,其中包含向 Vue 描述它应该在页面上呈现哪种节点的信息,包括对任何子节点的描述。用于手动编写render
语法
// type onlyh('div')// type + propsh('div', {})// type + omit props + children// Omit props does NOT support named slotsh('div', []) // arrayh('div', 'foo') // texth('div', h('br')) // vnodeh(Component, () => {}) // default slot// type + props + childrenh('div', {}, []) // arrayh('div', {}, 'foo') // texth('div', {}, h('br')) // vnodeh(Component, {}, () => {}) // default sloth(Component, {}, {}) // named slots// named slots without props requires explicit `null` to avoid ambiguityh(Component, null, {})
举个栗子
const App = {render() {return Vue.h('h1', {}, 'Hello Vue3js.cn')}}Vue.createApp(App).mount('#app')
都干了些啥
h 接收三个参数
- type 元素的类型
- propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
- children 子节点
export function h(type: any, propsOrChildren?: any, children?: any): VNode {if (arguments.length === 2) {if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {// single vnode without propsif (isVNode(propsOrChildren)) {return createVNode(type, null, [propsOrChildren])}// props without childrenreturn createVNode(type, propsOrChildren)} else {// omit propsreturn createVNode(type, null, propsOrChildren)}} else {if (isVNode(children)) {children = [children]}return createVNode(type, propsOrChildren, children)}}
_createVNode做的事情也很简单
function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,props: (Data & VNodeProps) | null = null,children: unknown = null,// 更新标志patchFlag: number = 0,// 自定义属性dynamicProps: string[] | null = null,// 是否是动态节点,(v-if v-for)isBlockNode = false): VNode {// type必传参数if (!type || type === NULL_DYNAMIC_COMPONENT) {if (__DEV__ && !type) {warn(`Invalid vnode type when creating vnode: ${type}.`)}type = Comment}// Class 类型的type标准化// class component normalization.if (isFunction(type) && '__vccOpts' in type) {type = type.__vccOpts}// class & style normalization.if (props) {// props 如果是响应式,clone 一个副本if (isProxy(props) || InternalObjectKey in props) {props = extend({}, props)}let { class: klass, style } = props// 标准化class, 支持 string , array, object 三种形式if (klass && !isString(klass)) {props.class = normalizeClass(klass)}// 标准化style, 支持 array ,object 两种形式if (isObject(style)) {// reactive state objects need to be cloned since they are likely to be// mutatedif (isProxy(style) && !isArray(style)) {style = extend({}, style)}props.style = normalizeStyle(style)}}// encode the vnode type information into a bitmapconst shapeFlag = isString(type)? ShapeFlags.ELEMENT: __FEATURE_SUSPENSE__ && isSuspense(type)? ShapeFlags.SUSPENSE: isTeleport(type)? ShapeFlags.TELEPORT: isObject(type)? ShapeFlags.STATEFUL_COMPONENT: isFunction(type)? ShapeFlags.FUNCTIONAL_COMPONENT: 0if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {type = toRaw(type)warn(`Vue received a Component which was made a reactive object. This can ` +`lead to unnecessary performance overhead, and should be avoided by ` +`marking the component with \`markRaw\` or using \`shallowRef\` ` +`instead of \`ref\`.`,`\nComponent that was made reactive: `,type)}// 构造 VNode 模型const vnode: VNode = {__v_isVNode: true,__v_skip: true,type,props,key: props && normalizeKey(props),ref: props && normalizeRef(props),scopeId: currentScopeId,children: null,component: null,suspense: null,dirs: null,transition: null,el: null,anchor: null,target: null,targetAnchor: null,staticCount: 0,shapeFlag,patchFlag,dynamicProps,dynamicChildren: null,appContext: null}normalizeChildren(vnode, children)// presence of a patch flag indicates this node needs patching on updates.// component nodes also should always be patched, because even if the// component doesn't need to update, it needs to persist the instance on to// the next vnode so that it can be properly unmounted later.// patchFlag 标志存在表示节点需要更新,组件节点一直存在 patchFlag,因为即使不需要更新,它需要将实例持久化到下一个 vnode,以便以后可以正确卸载它if (shouldTrack > 0 &&!isBlockNode &¤tBlock &&// the EVENTS flag is only for hydration and if it is the only flag, the// vnode should not be considered dynamic due to handler caching.patchFlag !== PatchFlags.HYDRATE_EVENTS &&(patchFlag > 0 ||shapeFlag & ShapeFlags.SUSPENSE ||shapeFlag & ShapeFlags.TELEPORT ||shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)) {// 压入 VNode 栈currentBlock.push(vnode)}return vnode}
总结
到这里,h 函数已经全部看完了,我们现在知道 h 叫法的由来,其函数内部逻辑只做参数检查,真正的主角是 _createVNode
_createVNode 做的事情有
- 标准化
propsclass - 给
VNode打上编码标记 - 创建
VNode - 标准化子节点
有的同学可能会有疑问🤔️,VNode 最后是怎么转换成真实的 DOM 呢?
这边有单独拉出来讲,请点VNode
