• home > webfront > ECMAS > vue3 >

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

    Author:zhoulujun Date:

    h 函数本质就是 createElement() ,h函数其实是createVNode的语法糖,返回的就是一个Js普通对象。在createVNode API 在创建Vnode的时候,会对Vnode的props、children、ref、class、style等属性进行规范梳理或者合并。

     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>
        <input 
          type="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 } = 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

    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,更加灵活,也更抽象。

    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.
     *
     * ```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

    export 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与withDirectives

    https://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;
    }


    render

    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系列—渲染流程

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