vue2升级vue3: h、createVNode、render、createApp使用
h、createVNode 杂乱笔记,凑合着看,不喜勿喷!
h 函数是什么
h 函数本质就是 createElement() 的简写,作用是根据配置创建对应的虚拟节点,在vue 中占有极其重要的地位!
在Vue2中,有个全局API:render函数。Vue内部回给这个函数传递一个h函数,用于创建Vnode的描述对象。
在Vue3中。将h函数独立出来,作为一个单独的API,它的作用仍保持原样:用于创建一个描述所渲染节点的Vnode描述对象。
javascript相较于模板语法,有更高的自由度。当使用模板太过臃肿的时候,比如多个if/else,就可以使用渲染函数h。
h 函数的配置
接收三个参数:type,props 和 children。具体查看官方文档:https://v3.cn.vuejs.org/guide/render-function.html#h-参数
1 2 3 4 5 | export declare function h( type: string, props?: RawProps | null , children?: RawChildren | RawSlots ): VNode; |
type
-
类型:String | Object | Function
-
详细:HTML 标签名、组件、异步组件或函数式组件 (注意:Vue3 不支持组件名用字符串表示了,必须直接使用组件名)
props
-
类型:Object
-
详细:与我们将在模板中使用的 attribute、prop 和事件相对应。可选
html元素的 attribute ,如 id name class,vue 的props参数。
children
-
类型:String | Object | Array
-
详细:children是子节点 VNode,使用 h() 生成,或者使用字符串来获取“文本 VNode”,或带有插槽的对象。可选。
html元素生成子元素,vue组件生成 slot default 插槽。
原理解析
在刚开始学习Vue的时候,我一直搞不懂render函数中h的使用方式。如果你也是一直通过HTML模板语法来搭建页面结构,可能也会对h函数不特别熟悉,下面可以一起学习下。
当我们创建一个组件时,一般都是通过HTML模板来描述UI部分,比如:
使用HTML标签:
1 2 3 4 5 6 7 8 9 | <template> <input type= "radio" :id= "branch" :value= "branch" name= "branch" v-model= "currentBranch" > <label : for = "branch" >{{ branch }}</label> </template> |
使用自定义组件标签:
1 2 3 | <template> <tree-item class = "item" :model= "treeData" @chang= "changeHandler" ></tree-item> </template> |
其实这些都可以将通过JS抽象为三部分,并用对象描述:
-
用于表示模板标签类型的type
-
传给模板的attribute、prop 和事件
-
标签包裹的子节点children
且子节点同样可以抽象为同样的结构。
而h函数就是做了这么一件事。给他传入type、props、children。它返回对应的Vnode描述对象。
案例说明:
1 2 3 4 5 | const PropsPanel = defineAsyncComponent(() => import ( './components/PropsPanel' )); import MySon from './son.vue' this .propsPanel = h(PropsPanel, { panelModel: {type: 'bar' }, },[h(MySon, {name: 'hhh' })]); |
异步加载模板,如:《vue2升级vue3:this.$createElement is not a function—动态组件升级》
开源案例:
Vue3 中 h 函数如何接收子组件$emit发送的事件
绑定的事件名需要加多一个on前(TSX)
1 2 3 4 5 | h(TableActionButtons, { //子组件 $emit 传递函数!!!!emit('start') onStart(data) { console.log(data); },}) |
Vue3 中 h 函数如何使用指令
v-show
1 | < div v-show="isActive">Content</ div > |
使h函数表述如下:
1 2 3 4 5 6 7 8 | render() { return h( "div" , { "directives" : [{ name: "show" , value: isActive }], }, "Content" ); } |
v-for
1 2 3 | < ul > < li v-for="item in items">{{ item.name }}</ li > </ ul > |
使h函数表述如下:
1 2 3 4 5 | render() { return h( 'ul' , this .items.map((item) => { return h( 'li' , item.name) })) } |
-
可以通过map函数代替v-for指令
-
通过map返回的Vnode,每一个都是不同的对象
v-on
直接 如Click,直接加上on,变为onClick 帮道到 props 属性里面即可
1 2 3 4 5 | render() { return h( 'button' , { onClick: onClick }) } |
Vue3 中 h 函数如何使用插槽
可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:
1 2 3 4 5 | render() { return h( 'div' , {}, this .$slots. default ({ text: this .message })) } |
可以通过this.$slot访问静态插槽的内容
如果需要传递状态,可以给this.$slots.default()函数传递一个对象参数
自定义组件
1 | <div><child v-slot: default = "slotProps" ><span>{{ slotProps.text }}</span></child></div> |
resolveComponent API会返回child组件的Vnode。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const { h, resolveComponent } = Vue render() { return h( 'div' , [ h( resolveComponent( 'child' ), {}, // 将 `slots` 以 { name: props => VNode | Array<VNode> } 的形式传递给子对象。 { default : (props) => Vue.h( 'span' , props.text) } ) ]) } |
Vue3 中 h 函数如何动态组件
1 | <component :is= "name" ></component> |
使h函数表述如下:
1 2 3 4 5 | const { h, resolveDynamicComponent } = Vue render() { const Component = resolveDynamicComponent( this .name) return h(Component) } |
可不可以直接创建一个Vnode描述对象
当然可以,只不过如果涉及Vnode的描述全部自己写的话,有点太累,而且容易出错。
我们先看下Vue内部定义的Vnode对象所包含的属性:
- __v_isVNode: *true*,内部属性,有该属性表示为Vnode
- __v_skip: true,内部属性,表示跳过响应式转换,reactive转换时会根据此属性进行判断
- isCompatRoot?: *true*,用于是否做了兼容处理的判断
- type: VNodeTypes,虚拟节点的类型
- props: (VNodeProps & ExtraProps) | *null*,虚拟节点的props
- key: *string* | *number* | *null*,虚拟阶段的key,可用于diff
- ref: VNodeNormalizedRef | *null*,虚拟阶段的引用
- scopeId: *string* | *null*,仅限于SFC(单文件组件),在设置currentRenderingInstance当前渲染实例时,一期设置
- slotScopeIds: *string*[] | *null*,仅限于单文件组件,与单文件组件的插槽有关
- children: VNodeNormalizedChildren,子节点
- component: ComponentInternalInstance | null,组件实例
- dirs: DirectiveBinding[] | null,当前Vnode绑定的指令
- transition: TransitionHooks<HostElement> | null,TransitionHooks
- DOM相关属性
- el: HostNode | *null*,宿主阶段
- anchor: HostNode | *null* // fragment anchor
- target: HostElement | *null* ,teleport target 传送的目标
- targetAnchor: HostNode | *null* // teleport target anchor
- staticCount: *number*,包含的静态节点的数量
- suspense 悬挂有关的属性
-
suspense: SuspenseBoundary | *null*
-
ssContent: VNode | *null*
-
ssFallback: VNode | *null*
- optimization only 用于优化的属性
- shapeFlag: *number*
- patchFlag: *number*
- dynamicProps: *string*[] | *null*
- dynamicChildren: VNode[] | *null*
- 根节点会有的属性
- appContext: AppContext | *null*,实例上下文
可以看到在Vue内部,对于一个Vnode描述对象的属性大概有二十多个,有些属性还必须经过规范梳理。
Vue为了给用于减轻一定的负担,但又不至于太封闭,就创建了渲染h。可以在用户需要的时候,通过h函数创建对应的Vnode即可。
这样就给为一些高阶玩家保留了自由发挥的空间。
renderSlot
Compiler runtime helper for rendering <slot/>
渲染父组件的 v-slot
1 2 3 4 5 6 7 | export declare function renderSlot( slots: Slots, name: string, props?: Data, fallback?: () => VNodeArrayChildren, noSlotted?: boolean ): VNode; |
createVNode
h函数其实是createVNode的语法糖,返回的就是一个Js普通对象。在createVNode API 在创建Vnode的时候,会对Vnode的props、children、ref、class、style等属性进行规范梳理或者合并。如果Type直接就是Vnode类型,则会返回深度克隆的Vnode对象。相较于HTML模板语法,使用h函数创建组件Vnode,更加灵活,也更抽象。
1 2 3 4 5 6 7 8 | function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null , children: unknown = null , patchFlag: number = 0, dynamicProps: string[] | null = null , isBlockNode = false ) |
_createVNode函数的主要职责:
-
梳理规范props中的class、style、child
-
创建Vnode的描述对象,并返回
-
对Vue2做兼容处理
使用和 createElement【h函数】神似
使用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | return props.mask ? createVNode( 'div' , { class : [ 'el-overlay' , props.overlayClass], style: { zIndex: props.zIndex, }, onClick: onMaskClick, onMousedown: (e: MouseEvent) => { // marking current mousedown target. if (props.mask) { mousedownTarget = e.target === e.currentTarget } }, onMouseup: (e: MouseEvent) => { if (props.mask) { mouseupTarget = e.target === e.currentTarget } }, }, [renderSlot(slots, 'default' )], PatchFlags.STYLE | PatchFlags.CLASS | PatchFlags.PROPS, [ 'onClick' , 'onMouseup' , 'onMousedown' ], ) : h( 'div' , { style: { zIndex: props.zIndex, position: 'fixed' , top: '0px' , right: '0px' , bottom: '0px' , left: '0px' , }, }, [renderSlot(slots, 'default' )], ) } |
PatchFlag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | /** * Patch flags are optimization hints generated by the compiler. * when a block with dynamicChildren is encountered during diff, the algorithm * enters "optimized mode". In this mode, we know that the vdom is produced by * a render function generated by the compiler, so the algorithm only needs to * handle updates explicitly marked by these patch flags. * * Patch flags can be combined using the | bitwise operator and can be checked * using the & operator, e.g. * * ```js * const flag = TEXT | CLASS * if (flag & TEXT) { ... } * ``` * * Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the * flags are handled during diff. */ export declare const enum PatchFlags { /** * Indicates an element with dynamic textContent (children fast path) */ TEXT = 1, /** * Indicates an element with dynamic class binding. */ CLASS = 2, /** * Indicates an element with dynamic style * The compiler pre-compiles static string styles into static objects * + detects and hoists inline static objects * e.g. style="color: red" and :style="{ color: 'red' }" both get hoisted as * const style = { color: 'red' } * render() { return e('div', { style }) } */ STYLE = 4, /** * Indicates an element that has non-class/style dynamic props. * Can also be on a component that has any dynamic props (includes * class/style). when this flag is present, the vnode also has a dynamicProps * array that contains the keys of the props that may change so the runtime * can diff them faster (without having to worry about removed props) */ PROPS = 8, /** * Indicates an element with props with dynamic keys. When keys change, a full * diff is always needed to remove the old key. This flag is mutually * exclusive with CLASS, STYLE and PROPS. */ FULL_PROPS = 16, /** * Indicates an element with event listeners (which need to be attached * during hydration) */ HYDRATE_EVENTS = 32, /** * Indicates a fragment whose children order doesn't change. */ STABLE_FRAGMENT = 64, /** * Indicates a fragment with keyed or partially keyed children */ KEYED_FRAGMENT = 128, /** * Indicates a fragment with unkeyed children. */ UNKEYED_FRAGMENT = 256, /** * Indicates an element that only needs non-props patching, e.g. ref or * directives (onVnodeXXX hooks). since every patched vnode checks for refs * and onVnodeXXX hooks, it simply marks the vnode so that a parent block * will track it. */ NEED_PATCH = 512, /** * Indicates a component with dynamic slots (e.g. slot that references a v-for * iterated value, or dynamic slot names). * Components with this flag are always force updated. */ DYNAMIC_SLOTS = 1024, /** * Indicates a fragment that was created only because the user has placed * comments at the root level of a template. This is a dev-only flag since * comments are stripped in production. */ DEV_ROOT_FRAGMENT = 2048, /** * SPECIAL FLAGS ------------------------------------------------------------- * Special flags are negative integers. They are never matched against using * bitwise operators (bitwise matching should only happen in branches where * patchFlag > 0), and are mutually exclusive. When checking for a special * flag, simply check patchFlag === FLAG. */ /** * Indicates a hoisted static vnode. This is a hint for hydration to skip * the entire sub tree since static content never needs to be updated. */ HOISTED = -1, /** * A special flag that indicates that the diffing algorithm should bail out * of optimized mode. For example, on block fragments created by renderSlot() * when encountering non-compiler generated slots (i.e. manually written * render functions, which should always be fully diffed) * OR manually cloneVNodes */ BAIL = -2 } |
createTextVNode
1 2 3 4 5 6 7 8 | export declare function createTextVNode( text?: string, flag?: number ): VNode; function createTextVNode(text = ' ' , flag = 0) { return createVNode(Text, null , text, flag); } |
createBlock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * Create a block root vnode. Takes the same exact arguments as `createVNode`. * A block root keeps track of dynamic nodes within the block in the * `dynamicChildren` array. * * @private */ export declare function createBlock( type: VNodeTypes | ClassComponent, props?: Record<string, any> | null , children?: any, patchFlag?: number, dynamicProps?: string[] ): VNode; |
toDisplayString
展示插值 {{ }}模板里边的内
1 2 3 4 5 6 | //shared /** * For converting {{ interpolation }} values to displayed strings. * @private */ export declare const toDisplayString: (val: unknown) => string; |
withCtx与withDirectives
https://github.com/vuejs/core/blob/060c5f1d0ae999cd8c8fb965e8526ffab17ac2d1/packages/runtime-core/src/vnode.ts#L326
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /** * Wrap a slot function to memoize current rendering instance * @private compiler helper */ export declare function withCtx(fn: Function, ctx?: ComponentInternalInstance | null ): Function; /** Runtime helper for applying directives to a vnode. Example usage: const comp = resolveComponent('comp') const foo = resolveDirective('foo') const bar = resolveDirective('bar') return withDirectives(h(comp), [ [foo, this.x], [bar, this.y] ]) */ /** * Adds directives to a VNode. */ export declare function withDirectives<T extends VNode>( vnode: T, directives: DirectiveArguments ): T; function withDirectives(vnode, directives) { const internalInstance = currentRenderingInstance; if (internalInstance === null ) { return vnode; } const instance = internalInstance.proxy; const bindings = vnode.dirs || (vnode.dirs = []); for ( let i = 0; i < directives.length; i++) { let [dir, value, arg, modifiers = shared.EMPTY_OBJ] = directives[i]; if (shared.isFunction(dir)) { dir = { mounted: dir, updated: dir }; } bindings.push({ dir, instance, value, oldValue: void 0, arg, modifiers }); } return vnode; } |
render
1 2 | export declare const render: RootRenderFunction<Element | ShadowRoot>; export declare type RootRenderFunction<HostElement = RendererElement> = (vnode: VNode | null , container: HostElement, isSVG?: boolean) => void; |
createApp
vue3以前我们会用new Vue()去创建应用
vue3引入createApp方法去创建。
我们会调用createApp方法,然后把我们定义的Vue实例对象作为参数传入,之后createApp方法会返回一个app对象。
下一步,我们会调用app对象的mount方法,把我们css选择器的元素传进去,这个就像我们之前的vue2的$mount方法一样
vue3的createApp会返回一个全新的app,可以很好地避免 全局(如plugins, mixins, prototype properties等等) 污染
const app = createApp({ render: h => h(App), data: () => ({ count: 0 }), methods: { inc() { this.count++; } } }); //挂载组件 app.mount('#app') // 组件渲染和未捕获错误配置的处理程序 app.config.errorHandler = (err, vm, info) => {} // 添加全局属性 app.config.globalProperties.$http = () => {} // 这里相当于挂载到Vue2的 Vue.prototype // 指定一种方法识别Vue之外定义的自定义元素 app.config.isCustomElement = tag => tag.startsWith('ion-') // 注册组件 app.component('my-component', {}) // 检索组件 const MyComponent = app.component('my-component') // 注册指令 app.directive('my-directive',{}) // 设置一个可以注入到应用程序内所有组件中的值。组件应使用inject来接收提供的值。 app.provide('user', 'administrator') // 卸载应用程序 app.unmount() // 安装vue插件 import MyPlugin from './plugins/MyPlugin' app.use(MyPlugin)
具体参看官网:https://vuejs.org/guide/essentials/application.html#app-configurations
推荐乐队:Vue3源码 | createApp都干了什么? https://juejin.cn/post/7032240868060823583
最后来一个图
此图来源于:vue3.0系列—渲染流程 https://juejin.cn/post/6934706558655791118
参考文章:
第七篇`Vue3 RunTimeCore`——高阶 `API` https://mdnice.com/writing/e1e7f78e912d49ee8f1c99b45262de19
Vue3使用h函数创建子组件(涉及到$emit,props的传递以及多个具名插槽的使用) https://blog.csdn.net/m0_46627730/article/details/123990678
vue3.0系列—渲染流程 https://juejin.cn/post/6934706558655791118
Vue3教程-使用Vue3新特性创建一个简单App https://www.jianshu.com/p/7f96a2b36188
转载本站文章《vue2升级vue3: h、createVNode、render、createApp使用》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8867.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2021-07-25 web自动化测试(2):选择selenium优势?与PhantomJS/QTP/Monkey对比
2021-07-25 web自动化测试(1):再谈UI发展史与UI、功能自动化测试