前言
一般vue组件都是用<template>写html, 但实际还可以在js代码中通过render函数生成dom. 最主要常见组件库也需要配合h函数使用。
render函数
render是组件的一个选项, 他的返回值会被作为组件的DOM结构.示例:
1 2 3 4 5 6 7 8
| import { defineComponent} from "vue"; const App = defineComponent({ render(){ return '123456789' } });
createApp(App).mount("#app");
|
同样可以插入dom元素:
1 2 3 4 5 6 7 8
| import { defineComponent } from "vue"; const App = defineComponent({ render(){ return '<h2>123456789</h2>' } });
createApp(App).mount("#app");
|
直接使用字符串写入会发现vue并没有解析dom,而是将内容当做字符串直接写入到元素表中去了。那么这里需要怎么插入HTML元素呢?这就需要用到vue里面的一个 VNode的东西。
VNode是vue对于DOM节点的一个描述,TS中是一个类型对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| export declare interface VNode<HostNode = RendererNode, HostElement = RendererElement, ExtraProps = { [key: string]: any; }> { type: VNodeTypes; props: (VNodeProps & ExtraProps) | null; key: string | number | symbol | null; ref: VNodeNormalizedRef | null;
scopeId: string | null; children: VNodeNormalizedChildren; component: ComponentInternalInstance | null; dirs: DirectiveBinding[] | null; transition: TransitionHooks<HostElement> | null; el: HostNode | null; anchor: HostNode | null; target: HostElement | null; targetAnchor: HostNode | null; suspense: SuspenseBoundary | null; shapeFlag: number; patchFlag: number; appContext: AppContext | null; }
|
VNode先不用研究,这里只用知道使用到了这个就行。
接下来要实现渲染HTML元素,就需要一个名为h的函数来调用这个VNode类型来完成。
h函数
示例
1 2 3 4 5 6 7 8
| import { defineComponent } from "vue"; const App = defineComponent({ render() { return h('h2', { style: {color: 'red'} }, '123456'); } });
createApp(App).mount("#app");
|
这样一个带有字体为红色样式的h2标签就生成了。
h函数源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
export function h(type: any, propsOrChildren?: any, children?: any): VNode { const l = arguments.length if (l === 2) { if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { if (isVNode(propsOrChildren)) { return createVNode(type, null, [propsOrChildren]) } return createVNode(type, propsOrChildren) } else { return createVNode(type, null, propsOrChildren) } } else { if (l > 3) { children = Array.prototype.slice.call(arguments, 2) } else if (l === 3 && isVNode(children)) { children = [children] } return createVNode(type, propsOrChildren, children) } }
|
所以可以明白:vue中的template其实并不是在写HTML,知识在写h函数来创建HTML。
实现创建vue项目的初始效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { createApp, defineComponent, h } from "vue"; import HelloWorld from "./components/HelloWorld.vue"; const img =require('./assets/logo.png');
const App = defineComponent({ render() { return h('div', { id: 'app' }, [ h('img', { alt: 'vue logo', src: img }), h(HelloWorld, { msg: 'Welcome to Your Vue.js + TypeScript App', age: 12 }) ]); } });
createApp(App).mount("#app");
|
上面的代码相当于下面的vue代码:
1 2 3 4 5 6
| <template> <div id="app"> <img src="./assets/logo.png" alt="vue logo"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" :age="12" /> </div> </template>
|
最终效果图
小记
由以上已经知道其实h函数在最后执行的还是createVNode函数,所以我们可以直接调用该函数来实现,不过需要注意的是这个函数的第三个参数必须是数组即可。
以上的代码就可以领写为下面这种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { createApp, defineComponent, h, createVNode } from "vue"; import HelloWorld from "./components/HelloWorld.vue"; const img =require('./assets/logo.png');
const App = defineComponent({ render() { return createVNode('div', { id: 'app' }, [ createVNode('img', { alt: 'vue logo', src: img }), createVNode(HelloWorld, { msg: 'Welcome to Your Vue.js + TypeScript App', age: 12 }) ]); } });
createApp(App).mount("#app");
|
注:使用JSX编译的代码里面其实是看不到h函数的,只会存在createVNode函数。
因为createVNode函数有一些帮助优化渲染组件的参数。
1 2 3
| export declare const createVNode: typeof _createVNode;
declare function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props?: (Data & VNodeProps) | null, children?: unknown, patchFlag?: number, dynamicProps?: string[] | null, isBlockNode?: boolean): VNode;
|
比如上面的后几位参数:patchFlag、dynamicProps、isBlockNode
__END__