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-参数
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—动态组件升级》
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 theflags 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
版权声明: 本文为 InfoQ 作者【zhoulujun】的原创文章。
原文链接:【http://xie.infoq.cn/article/fcad15a794b31cfb617f988a4】。文章转载请联系作者。
评论