跳至主要內容

9. 实现 runtime-core


9. 实现 runtime-core

1.创建 runtime-core 包

runtime-core 不关心运行平台。

runtime-core/package.json

{
	"name": "@vue/runtime-core",
	"module": "dist/runtime-core.esm-bundler.js",
	"types": "dist/runtime-core.d.ts",
	"files": ["index.js", "dist"],
	"buildOptions": {
		"name": "VueRuntimeCore",
		"formats": ["esm-bundler", "cjs"]
	}
}

runtime-core中需要依赖 @vue/shared@vue/reactivity

pnpm install @vue/shared@workspace @vue/reactivity@workspace --filter @vue/runtime-core

最后我们将开发环境下的打包入口改为 runtime-dom

2.虚拟节点的实现

形状标识

通过组合可以描述虚拟节点的类型

export const enum ShapeFlags { // vue3提供的形状标识
	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
}

createVNode 实现

export function isVNode(value: any) {
	return value ? value.__v_isVNode === true : false;
}
export const createVNode = (type, props, children = null) => {
	const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;
	const vnode = {
		__v_isVNode: true,
		type,
		props,
		key: props && props['key'],
		el: null,
		children,
		shapeFlag
	};
	if (children) {
		let type = 0;
		if (Array.isArray(children)) {
			type = ShapeFlags.ARRAY_CHILDREN;
		} else {
			children = String(children);
			type = ShapeFlags.TEXT_CHILDREN;
		}
		vnode.shapeFlag |= type;
		// 如果shapeFlag为9 说明元素中包含一个文本
		// 如果shapeFlag为17 说明元素中有多个子节点
	}
	return vnode;
};

createVNode 的写法比较死板,我们让他变的更灵活些

h 实现

export function h(type, propsOrChildren?, children?) {
	const l = arguments.length;
	if (l === 2) {
		// 只有属性,或者一个元素儿子的时候
		if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
			if (isVNode(propsOrChildren)) {
				// h('div',h('span'))
				return createVNode(type, null, [propsOrChildren]);
			}
			return createVNode(type, propsOrChildren); // h('div',{style:{color:'red'}});
		} else {
			// 传递儿子列表的情况
			return createVNode(type, null, propsOrChildren); // h('div',null,[h('span'),h('span')])
		}
	} else {
		if (l > 3) {
			// 超过3个除了前两个都是儿子
			children = Array.prototype.slice.call(arguments, 2);
		} else if (l === 3 && isVNode(children)) {
			children = [children]; // 儿子是元素将其包装成 h('div',null,[h('span')])
		}
		return createVNode(type, propsOrChildren, children); // h('div',null,'erxiao')
	}
}
// 注意子节点是:数组、文本、null

3.createRenderer 实现

render 方法就是采用 runtime-dom 中提供的方法将虚拟节点转化成对应平台的真实节点渲染到指定容器中。

export function createRenderer(options) {
	const {
		insert: hostInsert,
		remove: hostRemove,
		patchProp: hostPatchProp,
		createElement: hostCreateElement,
		createText: hostCreateText,
		setText: hostSetText,
		setElementText: hostSetElementText,
		parentNode: hostParentNode,
		nextSibling: hostNextSibling
	} = options;
	const patch = (n1, n2, container) => {
		// 初始化和diff算法都在这里喲
	};
	const render = (vnode, container) => {
		if (vnode == null) {
			if (container._vnode) {
			} // 卸载
		} else {
			patch(container._vnode || null, vnode, container); // 初始化和更新
		}
		container._vnode = vnode;
	};
	return {
		render
	};
}

4.创建真实 DOM

const mountChildren = (children, container) => {
	for (let i = 0; i < children.length; i++) {
		patch(null, children[i], container);
	}
};
const mountElement = (vnode, container) => {
	const { type, props, shapeFlag } = vnode;
	let el = (vnode.el = hostCreateElement(type)); // 创建真实元素,挂载到虚拟节点上
	if (props) {
		// 处理属性
		for (const key in props) {
			// 更新元素属性
			hostPatchProp(el, key, null, props[key]);
		}
	}
	if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
		// 文本
		hostSetElementText(el, vnode.children);
	} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
		// 多个儿子
		mountChildren(vnode.children, el);
	}
	hostInsert(el, container); // 插入到容器中
};
const patch = (n1, n2, container) => {
	// 初始化和diff算法都在这里喲
	if (n1 == n2) {
		return;
	}
	if (n1 == null) {
		// 初始化的情况
		mountElement(n2, container);
	} else {
		// diff算法
	}
};

5.卸载 DOM

createRenderer(renderOptions).render(null, document.getElementById('app'));

const unmount = (vnode) => {
	hostRemove(vnode.el);
};
const render = (vnode, container) => {
	if (vnode == null) {
		if (container._vnode) {
			// 卸载
			unmount(container._vnode); // 找到对应的真实节点将其卸载
		}
	} else {
		patch(container._vnode || null, vnode, container); // 初始化和更新
	}
	container._vnode = vnode;
};

6.优化调用方法

export const render = (vnode, container) => {
	createRenderer(renderOptions).render(vnode, container);
};
export * from '@vue/runtime-core';

这样在页面中可以直接调用render方法进行渲染啦~