跳到主要内容

官方模拟版

2023年06月11日
柏拉文
越努力,越幸运

一、packages/runtime-core/src/index.ts


export * from './h';
export * from './vnode';
export * from './apiWatch';
export * from './scheduler';

二、packages/runtime-core/src/h.ts


import { isObject } from '@vue/shared';
import { VNode, createVNode, isVNode } from './vnode';

export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length;
if (l === 2) {
if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren]);
}
return createVNode(type, propsOrChildren);
} else {
return createVNode(type, 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);
}
}

三、packages/runtime-core/src/vnode.ts


import { isArray, isFunction, isObject, isString } from '@vue/shared';
import { normalizeClass } from 'packages/shared/src/normalizeProp';
import { ShapeFlags } from 'packages/shared/src/shapeFlags';

export const Text = Symbol.for('v-text');
export const Comment = Symbol.for('v-cmt');
export const Fragment = Symbol.for('v-fgt');

export interface VNode {
__v_isVNode: boolean;
type: any;
children: any;
shapeFlag: number;
}

export function isVNode(value: any): value is VNode {
return value ? value.__v_isVNode === true : false;
}

export function createVNode(type: any, props?: any, children?: any): VNode {
if (props) {
let { class: klass, style } = props;
if (klass && !isString(klass)) {
props.class = normalizeClass(klass);
}
}

const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: 0;
return createBaseVNode(type, props, children, shapeFlag);
}

function createBaseVNode(type, props, children, shapeFlag): VNode {
const vnode = {
__v_isVNode: true,
type,
props,
children,
shapeFlag
} as VNode;
normalizeChildren(vnode, children);
return vnode;
}

export function normalizeChildren(vnode: VNode, children: any) {
let type = 0;
if (children == null) {
children = null;
} else if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN;
} else if (typeof children === 'object') {
} else if (isFunction(children)) {
} else {
children = String(children);
type = ShapeFlags.TEXT_CHILDREN;
}

vnode.children = children;
vnode.shapeFlag |= type;
}

四、packages/shared/src/normalizeProp.ts


import { isArray, isObject, isString } from '.';

export function normalizeClass(value: any): string {
let res = '';
if (isString(value)) {
res = value;
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
const normalized = normalizeClass(value[i]);
if (normalized) {
res += normalized + ' ';
}
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + ' ';
}
}
}
return res.trim();
}

五、packages/shared/src/shapeFlags.ts


export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}

六、packages/shared/src/index.ts


export function isArray(data: any) {
return Array.isArray(data);
}

export function isObject(value: unknown) {
return value !== null && typeof value === 'object';
}

export function hashChanged(value: any, oldValue: any): boolean {
return !Object.is(value, oldValue);
}

export function isFunction(value: unknown): value is Function {
return typeof value === 'function';
}

export const extend = Object.assign;

export const isString = (value: any): value is string =>
typeof value === 'string';

测试用例


h text

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3 h-text</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<script type="module">
const { h, render } = Vue;

const vnode = h(
'div',
{
class: 'test'
},
'hello render'
);

console.log(vnode);
</script>
</body>
</html>

h Text

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>h-text</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module">
const { h, render, Text } = Vue;

const vnode = h(Text, '这是一个 Text');
console.log('vnode', vnode);

render(vnode, document.querySelector('#app'));
</script>
</body>
</html>

h Comment

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>h-comment</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module">
const { h, render, Comment } = Vue;

const vnode = h(Comment, '这是一个 Comment');
console.log('vnode', vnode);

render(vnode, document.querySelector('#app'));
</script>
</body>
</html>

h Fragment

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>h-fragment</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module">
const { h, render, Fragment } = Vue;

const vnode = h(Fragment, '这是一个 Fragment');
console.log('vnode', vnode);

render(vnode, document.querySelector('#app'));
</script>
</body>
</html>

h component

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>h-component</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module">
const { h, render } = Vue;

const component = {
render() {
const vnode = h('div', '这是一个 component');
console.log('vnode1', vnode);
return vnode;
}
};

const vnode = h(component);
console.log('vnode2', vnode);

render(vnode, document.querySelector('#app'));
</script>
</body>
</html>

h element class

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3-Next-Mini h-element-class</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<script type="module">
const { h } = Vue;

const vnode1 = h(
'div',
{
class: {
class1: true,
class2: false
}
},
'增强的 class'
);

console.log(vnode1);

const vnode2 = h(
'div',
{
class: [
'class1',
{
class2: true
},
'class3'
]
},
'增强的 class'
);

console.log(vnode2);

const vnode3 = h(
'div',
{
class: 'class' + 1
},
'增强的 class'
);

console.log(vnode3);
</script>
</body>
</html>

h element style

h eleement array chilidren

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>h-element-array</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<script type="module">
const { h, render } = Vue;

const vnode = h(
'div',
{
class: 'test'
},
[h('p', 'p1'), h('p', 'p2'), h('p', 'p3')]
);

console.log(vnode);
</script>
</body>
</html>