本文使用的vue版本为3.0.2
- createApp() & mount()
- setup()
- render() h()
- Virtual Dom
- 生命周期hooks
- Proxy代理
- reactive() ref()
- computed()
- watch()
- provide() inject()
- directives()
- components()
如何在组合式API中使用渲染函数(render)?
import { h } from 'vue'
export default {
setup() {
return () => h('div', ['Hello,Vue3'])
}
}
在上一篇文章中我们已经说过,实例setup
选项的返回值可以有两种:
- Object
- Function
而返回一个函数的写法,就类似于直接使用实例的render
选项:
<script>
import { h } from "vue";
export default {
setup() {
return () => h("div", ["Hello,Vue3"]);
}
};
</script>
/* 等价于 */
<script>
import { h } from "vue";
export default {
render() {
return h("div", ["Hello,Vue3"]);
}
};
</script>
那么,h()
这个方法又是什么呢?
h()
-
返回值:VNode
-
目的:用于手动编写的
render
函数 -
参数:
[type,props,children]
-
type
:必须,创建的VNode类型(HTML标签,组件,异步组件)。使用null
创建注释节点。 -
props
:可选,一个对象,可为VNode配置attribute、prop 和事件等。 -
Children
:可选,子VNode或HTML标签内容。同样由h()
创建。
-
源码时间
h()
部分的源码很简单:
// packages/runtime-core/src/h.ts
// 上边做了若干次重载
// 实际实现
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// 无props vnode
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// 无children vnode
return createVNode(type, propsOrChildren)
} else {
// 忽略props
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)
}
}
实际上,在h()
内部,是通过不同的参数,调用createVNode
创建不同的VNode。
然后包裹一层匿名函数,作为setup()
的返回值,最后渲染成dom。
实现
function isObject(val) {
return val !== null && typeof val === 'object'
}
function isArray(val) {
return Array.isArray(val)
}
function isVNode(val) {
return val.isVNode === true
}
// 校验入参调用createVNode
function h(type, propsOrChildren, children) {
const l = arguments.length
// 简化版:只考虑type为HTML标签名
if(typeof type !== 'string') return
const ele = document.createElement(type)
type = ele.outerHTML
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// 无props,数组children
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// 无children
return createVNode(type, propsOrChildren)
} else {
// 无props,单个children
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)
}
}
对之前的内容的内容进行补全:
// 创建VNode
function createVNode(temp, props, children) {
// 维护一个栈,通过进栈出栈匹配嵌套标签
const stack = [];
// root节点
let obj;
let content = temp;
while (content.length) {
if (content.indexOf("<") > 0 && content[0] !== '<') {
// 以文字开头
const index = content.indexOf("<");
let text = content.substring(0, index);
content = content.substring(index);
if (stack.length) {
stack[stack.length - 1].children.push(text);
} else {
obj.children.push(text);
}
} else if (content.indexOf("<") < 0) {
// 纯文字
if (stack.length) {
stack[stack.length - 1].children.push(content);
} else {
obj = {
tag: "",
children: [],
};
obj.children.push(content);
}
content = "";
} else {
// 以标签开头
const endIndex = content.indexOf(">");
let currentTag = content.substring(1, endIndex);
if (currentTag.indexOf("/") < 0) {
// 开始标签
stack.push({
tag: currentTag,
children: [],
});
} else {
// 结束标签
let child = stack[stack.length - 1];
obj = child;
stack.pop();
}
content = content.substring(endIndex + 1);
}
}
obj.isVNode = true
if (props) {
// 处理组件props
}
if (children) {
obj.children = children
}
return obj;
}
在线demo(完整代码可打开控制台在mini-vue.js
中找到)