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. 【译】NoClassDefFoundError和ClassNotFoundException的不同(转)

    https://www.jianshu.com/p/93d0db07d2e3 本文翻译自:Difference between NoClassDefFoundError vs ClassNotFoun ...

  2. Codeforces Round #698 (Div. 2) A~C题解

    写在前边 链接:Codeforces Round #698 (Div. 2) 又是自闭的一场比赛,\(C\)题补了一天终于明白了一些,真的好自闭好自闭. 今晚还有一场,加油喽. A. Nezzar a ...

  3. Android 输入系统介绍

    目录 一.目的 二.环境 三.相关概念 3.1 输入设备 3.2 UEVENT机制 3.3 JNI 3.4 EPOLL机制 3.5 INotify 四.详细设计 4.1 结构图 4.2 代码结构 4. ...

  4. Vue3 第三章

    Vite目录 public 下面的不会被编译 可以存放静态资源 assets 下面可以存放可编译的静态资源 components 下面用来存放我们的组件 App.vue 是全局组件 main ts 全 ...

  5. VisionPro学习笔记(5)——极轴展开工具PolarUnwrapTool

    如果需要了解其他图像处理的文章,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice ...

  6. Maven安装与配置【idea2022版本】

    一.maven下载 https://maven.apache.org/download.cgi 下载完毕后解压,注意解压路径不要有中文 二.环境变量 在环境变量Path里面新建(自己的maven的bi ...

  7. jmeter编写java脚本

    jmeter开发java脚本主要的依赖包有三个如下图 步骤1 :打开idea,创建一个project,导入上图依赖包 步骤2:创建一个类,继承AbstractJavaSamplerClient类,并实 ...

  8. 吉特日化MES系统&各类化妆品检验标准汇总

    在日化行业中,生产配料过程中,对产品的检验主要分为四大类: (1) 感官指标 (2) 理化指标 (3) 微生物指标 (4) 毒理指标 根据每个产品的不同,其指标会有所不同

  9. [ABC299F] Square Subsequence

    Problem Statement You are given a string $S$ consisting of lowercase English letters. Print the numb ...

  10. The fourth day learning summary

    一.for 循环循环就是重复做某件事,for循环是python提供第二种循环机制(第一种是while循环),理论上for循环能做的事情,while循环都可以做.目的:之所以要有for循环,是因为for ...