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

vue2升级vue3: h、createVNode、render、createApp使用的更多相关文章

  1. vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9

    项目从vue2 升级vue3,VueI18n需要做适当的调整.主要是Vue I18n v8.x 到Vue I18n v9 or later 的变化,其中初始化: 具体可以参看:https://vue- ...

  2. vue2升级vue3:Vue Demij打通vue2与vue3壁垒,构建通用组件

    如果你的vue2代码之前是使用vue-class-component 类组件模式写的.选择可以使用 https://github.com/facing-dev/vue-facing-decorator ...

  3. vue2升级vue3指南(二)—— 语法warning&error篇

    本文总结了vue2升级vue3可能会遇到的语法警告和错误,如果想知道怎样升级,可以查看我的上一篇文章:vue2升级vue3指南(一)-- 环境准备和构建篇 Warning 1.deep /deep/和 ...

  4. vue2升级vue3:vue-i18n国际化异步按需加载

    vue2异步加载之前说过,vue3还是之前的方法,只是把 i18n.setLocaleMessage改为i18n.global.setLocaleMessage 但是本文还是详细说一遍: 为什么需要异 ...

  5. uniapp项目vue2升级vue3简单记录

    看到好多开源项目都升级了vue3,看文章说vue3性能升级很多,而且组合式api很香,遂把最近开发的自助洗车app升级下,在此记录下出现的问题. uniapp升级vue3官方指南 我是先去vue官网看 ...

  6. vue2升级vue3指南(一)—— 环境准备和构建篇

    1.nodejs和npm 注意二者的版本,版本过低需要升级,本人升级后的版本如下: $ node -v v16.15.1 $ npm -v 8.11.0 2.package.json 和依赖升级 由于 ...

  7. Vue2和Vue3技术整理1 - 入门篇 - 更新完毕

    Vue2 0.前言 首先说明:要直接上手简单得很,看官网熟悉大概有哪些东西.怎么用的,然后简单练一下就可以做出程序来了,最多两天,无论Vue2还是Vue3,就都完全可以了,Vue3就是比Vue2多了一 ...

  8. Vue2 到 Vue3,重温这 5 个常用的 API

    距离Vue3发布已经过去一年多时间了,从Vue2到Vue3是一个不小的升级,包括周边生态等.虽然目前大多数开发者们在使用的仍旧以Vue2为准,但Vue3显然是Vue开发者们未来必须面对的,而且前不久V ...

  9. vue2和vue3配置全局自定义参数及vue3动态绑定ref

    在 Vue2.x 中我们可以通过 Vue.prototype 添加全局属性 property.但是在 Vue3.x 中需要将 Vue.prototype 替换为 config.globalProper ...

  10. vue2和vue3生命周期的区别

    概念 首先,我们了解一下"生命周期"这个词.通俗的来说,生命周期就是一个事务从出生到消失的过程.例如,一个人从出生到去世.在vue中,vue的生命周期是指,从创建vue对象到销毁v ...

随机推荐

  1. 使用单卡v100 32g或更低显存的卡,使用peft工具qlora或lora混合精度训练大模型chatGLM2-6b,torch混合精度加速稳定训练,解决qlora loss变成nan的问题!

    最近新换了工作,以后的工作内容会和大模型相关,所以先抽空跑了一下chatGLM2-6b的demo,使用Qlora或lora微调模型 今天简单写个文档记录一下,顺便也是一个简单的教程,并且踩了qlora ...

  2. Util应用框架基础(六) - 日志记录(四) - 写入 Exceptionless

    本文是Util应用框架日志记录的第四篇,介绍安装和写入 Exceptionless 日志系统的配置方法. Exceptionless 是一个日志管理系统,使用 Asp.Net Core 开发,比 Se ...

  3. 不要轻易定义指向std::vector中的元素的指针

    类应该是被封装的,类的用户通过接口使用类提供的功能,而不必关心类的内部如何实现.然而,C++标准库容器 std::vector 的实现渗透到了接口中来.对于以下代码: const int pushNu ...

  4. MDI窗体,打开子窗口的时候关闭其他子窗口及去除MainMenuStrip上自动产生的图标

    去除MDI子窗体最大化后在MainMenuStrip上自动产生的图标和最大化.最小化以及关闭按钮在MainMenuStrip的ItemAdded事件中添加代码如下: 1 private void me ...

  5. Bert-vits2新版本V2.1英文模型本地训练以及中英文混合推理(mix)

    中英文混合输出是文本转语音(TTS)项目中很常见的需求场景,尤其在技术文章或者技术视频领域里,其中文文本中一定会夹杂着海量的英文单词,我们当然不希望AI口播只会念中文,Bert-vits2老版本(2. ...

  6. liunx二进制包安装5.6MySQL数据库

    官网下载对应的二进制版本安装包 https://downloads.mysql.com/archives/community/ # 解压二进制压缩包 [root@localhost ~]# wget ...

  7. 轻量型 Web SCADA 组态软件 TopLink

    图扑物联 发布了一款工业物联网边缘侧应用场景的轻量型 Web SCADA 组态软件 Iotopo TopLink.该产品采用 B/S 架构,提供 Web 管理界面,软件包大小仅 23MB,无需安装客户 ...

  8. python tkinter使用(十一)

    python tkinter使用(十一) 本篇文章主要讲下tkinter 窗口的一些属性,以及实现无法关闭的窗口中遇到的一些问题. #!/usr/bin/python3 # -*- coding: U ...

  9. 通过JDK动态代理类实现一个类中多种方法的不同增强

    1.为什么说JDK动态代理必须要实现当前父接口才能使用 JDK动态代理是基于接口的代理,它要求目标类(被代理的类)必须实现一个或多个接口.这是因为JDK动态代理是通过创建目标类的接口的代理对象来实现的 ...

  10. Rabbitmq从安装到简单入门

    1:Rabbitmq是什么? RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件). 它由以高性能.健壮以及可伸缩性出名的 Erlang 写成. 2:它的优点 ...