写点什么

vue2 升级 vue3: h、createVNode、render、createApp 使用

作者:zhoulujun
  • 2022 年 7 月 25 日
  • 本文字数:7331 字

    阅读完需:约 24 分钟

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-参数


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 标签:


<template><inputtype="radio":id="branch":value="branch"name="branch"v-model="currentBranch"><label :for="branch">{{ branch }}</label></template>使用自定义组件标签:


<template><tree-item class="item" :model="treeData" @chang="changeHandler"></tree-item></template>其实这些都可以将通过 JS 抽象为三部分,并用对象描述:


用于表示模板标签类型的 type


传给模板的 attribute、prop 和事件


标签包裹的子节点 children


且子节点同样可以抽象为同样的结构。


而 h 函数就是做了这么一件事。给他传入 type、props、children。它返回对应的 Vnode 描述对象。


案例说明: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—动态组件升级》


开源案例:https://github.com/Tencent/tdesign-vue-next/blob/7c567973925fe970a04fa6fa16d073921f1f3850/src/dialog/plugin.tsx


https://github.com/zhoulujun/bkui-vue3/blob/5a70171bbd652198b8f41187f8969c4cdf947eab/packages/info-box/src/index.tsx


Vue3 中 h 函数如何接收子组件 $emit 发送的事件绑定的事件名需要加多一个 on 前(TSX)


h(TableActionButtons, {//子组件 $emit 传递函数!!!!emit('start')onStart(data) {console.log(data);},})Vue3 中 h 函数如何使用指令 v-show<div v-show="isActive">Content</div>使 h 函数表述如下:


render() {return h("div", {"directives": [{name: "show",value: isActive}],}, "Content");}v-for<ul><li v-for="item in items">{{ item.name }}</li></ul>使 h 函数表述如下:


render() {return h('ul', this.items.map((item) => {return h('li', item.name)}))}可以通过 map 函数代替 v-for 指令


通过 map 返回的 Vnode,每一个都是不同的对象


v-on 直接 如 Click,直接加上 on,变为 onClick 帮道到 props 属性里面即可


render() {return h('button', {onClick: onClick})}


Vue3 中 h 函数如何使用插槽可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:


render() {return h('div', {}, this.slots.default({text: this.message}))}可以通过this.slot 访问静态插槽的内容


如果需要传递状态,可以给 this.$slots.default()函数传递一个对象参数


自定义组件<div><child v-slot:default="slotProps"><span>{{ slotProps.text }}</span></child></div>resolveComponent API 会返回 child 组件的 Vnode。


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 函数如何动态组件<component :is="name"></component>使 h 函数表述如下:


const { h, resolveDynamicComponent } = Vuerender() {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,虚拟节点的 propskey: string | number | null,虚拟阶段的 key,可用于 diffref: VNodeNormalizedRef | null,虚拟阶段的引用 scopeId: string | null,仅限于 SFC(单文件组件),在设置 currentRenderingInstance 当前渲染实例时,一期设置 slotScopeIds: string[] | null,仅限于单文件组件,与单文件组件的插槽有关 children: VNodeNormalizedChildren,子节点 component: ComponentInternalInstance | null,组件实例 dirs: DirectiveBinding[] | null,当前 Vnode 绑定的指令 transition: TransitionHooks<HostElement> | null,TransitionHooksDOM 相关属性 el: HostNode | null,宿主阶段 anchor: HostNode | null // fragment anchortarget: HostElement | null ,teleport target 传送的目标 targetAnchor: HostNode | null // teleport target anchorstaticCount: number,包含的静态节点的数量 suspense 悬挂有关的属性 suspense: SuspenseBoundary | null


ssContent: VNode | null


ssFallback: VNode | null


optimization only 用于优化的属性 shapeFlag: numberpatchFlag: numberdynamicProps: string[] | nulldynamicChildren: VNode[] | null 根节点会有的属性 appContext: AppContext | null,实例上下文可以看到在 Vue 内部,对于一个 Vnode 描述对象的属性大概有二十多个,有些属性还必须经过规范梳理。


Vue 为了给用于减轻一定的负担,但又不至于太封闭,就创建了渲染 h。可以在用户需要的时候,通过 h 函数创建对应的 Vnode 即可。


这样就给为一些高阶玩家保留了自由发挥的空间。


renderSlotCompiler runtime helper for rendering <slot/>


渲染父组件的 v-slot


export declare function renderSlot(slots: Slots,name: string,props?: Data,fallback?: () => VNodeArrayChildren,noSlotted?: boolean): VNode;


createVNodeh 函数其实是 createVNode 的语法糖,返回的就是一个 Js 普通对象。在 createVNode API 在创建 Vnode 的时候,会对 Vnode 的 props、children、ref、class、style 等属性进行规范梳理或者合并。如果 Type 直接就是 Vnode 类型,则会返回深度克隆的 Vnode 对象。相较于 HTML 模板语法,使用 h 函数创建组件 Vnode,更加灵活,也更抽象。


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 函数】神似


使用案例 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/**


  • 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.

  • 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}


createTextVNodeexport declare function createTextVNode(text?: string,flag?: number): VNode;


function createTextVNode(text = ' ', flag = 0) {return createVNode(Text, null, text, flag);}


createBlock/**


  • 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 展示插值 {{ }}模板里边的内


//shared/**


  • For converting {{ interpolation }} values to displayed strings.

  • @private*/export declare const toDisplayString: (val: unknown) => string;


withCtx 与 withDirectiveshttps://github.com/vuejs/core/blob/060c5f1d0ae999cd8c8fb965e8526ffab17ac2d1/packages/runtime-core/src/vnode.ts#L326


/**


  • 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;}


renderexport declare const render: RootRenderFunction<Element | ShadowRoot>;export declare type RootRenderFunction<HostElement = RendererElement> = (vnode: VNode | null, container: HostElement, isSVG?: boolean) => void;


createAppvue3 以前我们会用 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 系列—渲染流程


此图来源于: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

发布于: 4 小时前阅读数: 11
用户头像

zhoulujun

关注

还未添加个人签名 2021.06.25 加入

还未添加个人简介

评论

发布
暂无评论
vue2升级vue3: h、createVNode、render、createApp使用_Vue3_zhoulujun_InfoQ写作社区