<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件的实现原理</title>
<style>
.bgColor {
background-color: deeppink;
}
.fontColor {
color: #fff;
font-size: 24px;
}
p {
border: 1px dashed #ccc;
padding: 6px;
}
</style>
<!-- <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script> -->
<script src="../lib/reactivity.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
// 引入响应式方法
const { reactive, ref, effect, shallowReactive, shallowReadonly } =
VueReactivity;
/**
* 定义文本节点和注释节点
*/
const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();
const newVnode1 = {
type: Text,
children: "我是文本内容 ",
};
const newVnode2 = {
type: Comment,
children: "我是注释内容",
};
const fragmentNode = {
type: "ul",
children: [
{
type: Fragment,
children: [
{ type: "li", children: "item 1" },
{ type: "li", children: "item 2" },
{ type: "li", children: "item 3" },
],
},
],
};
// 任务缓存队列
const queue = new Set();
let isFlushing = false;
const p = Promise.resolve();
function queueJob(job) {
queue.add(job);
if (!isFlushing) {
isFlushing = true;
p.then(() => {
try {
queue.forEach((job) => job());
} finally {
isFlushing = false;
queue.length = 0;
}
});
}
}
// 全局变量
let currentInstance = null;
function setCurrentInstance(instance) {
currentInstance = instance;
}
function onMounted(fn) {
if (currentInstance) {
currentInstance.mounted.push(fn);
} else {
console.error("onMounted 函数只能在 setup 中调用");
}
}
/**
* 自定义渲染器
*/
function createRenderer(options) {
const {
createElement,
insert,
setElementText,
patchProps,
createText,
setText,
} = options;
/**
* 卸载
*/
function unmount(vnode) {
if (vnode.type === Fragment) {
vnode.children.forEach((c) => unmount(c));
return;
}
const parent = vnode.el.parentNode;
if (parent) {
parent.removeChild(vnode.el);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
// 删除旧节点
if (container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
/**
* 打补丁
*/
function patch(n1, n2, container, anchor) {
if (n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
const { type } = n2;
// console.log(type);
if (typeof type === "string") {
if (!n1) {
mountElement(n2, container, anchor);
} else {
patchElement(n1, n2);
}
} else if (type === Text) {
// console.log("文本节点");
// 文本节点
if (!n1) {
const el = (n2.el = createText(n2.children));
insert(el, container);
} else {
const el = (n2.el = n1.el);
if (n2.children !== n1.children) {
setText(el, n2.children);
}
}
} else if (type === Fragment) {
// 片断
if (!n1) {
// 将Fragment的 children逐个挂载
n2.children.forEach((c) => patch(null, c, container));
} else {
patchChildren(n1, n2, container);
}
} else if (typeof type === "object") {
if (!n1) {
// 挂载组件
mountComponent(n2, container, anchor);
} else {
// 更新组件
patchComponent(n1, n2, anchor);
}
}
}
/**
* 更新组件
*/
function patchComponent(n1, n2, anchor) {
const instance = (n2.component = n1.component);
const { props } = instance;
if (hasPropsChanged(n1.props, n2.props)) {
const [nextProps] = resolveProps(n2.type.props, n2.props);
for (const k in nextProps) {
props[k] = nextProps[k];
}
for (const k in props) {
if (!(k in nextProps)) {
delete props[k];
}
}
}
}
function hasPropsChanged(prevProps, nextProps) {
const nextKeys = Object.keys(nextProps);
if (nextKeys.length !== Object.keys(prevProps).length) {
return true;
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i];
if (nextProps[key] !== prevProps[key]) {
return true;
}
}
return false;
}
/**
* 挂载组件
*/
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const {
render: renderOld,
data,
props: propsOption,
beforeCreate,
created,
beforeMount,
mounted,
beforeUpdate,
updated,
setup,
} = componentOptions;
let render = renderOld;
beforeCreate && beforeCreate();
// 构造响应式数据
const state = reactive(data());
// 解析出 props 数据与 attrs 数据
const [props, attrs] = resolveProps(propsOption, vnode.props);
console.log("props", props);
console.log("attrs", attrs);
const slots = vnode.children || {};
// 定义组件实例
const instance = {
state,
isMounted: false,
subTree: null,
props: shallowReactive(props),
slots,
mounted: [],
};
function emit(event, ...payload) {
const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
const handler = instance.props[eventName];
if (handler) {
handler(...payload);
} else {
console.error("事件不存在");
}
}
// 新增处理 setup 的逻辑
const setupContext = { attrs, emit, slots };
setCurrentInstance(instance);
const setupResult = setup(
shallowReadonly(instance.props),
setupContext
);
setCurrentInstance(null);
let setupState = null;
if (typeof setupResult === "function") {
if (render) {
console.error("setup 函数返回渲染函数,render 选项将被忽略");
}
render = setupResult;
} else {
setupState = setupContext;
}
vnode.component = instance;
// 创建渲染上下文对象,本质是组件实例的代理
const renderContext = new Proxy(instance, {
get(t, k, r) {
const { state, props, slots } = t;
if (k === "$slots") {
return slots;
}
if (state && k in state) {
return state[k];
} else if (k in props) {
return props[k];
} else if (setupState && k in setupState) {
return setupState[k];
} else {
console.error("不存在");
}
},
set(t, k, v, r) {
const { state, props } = t;
if (state && k in state) {
state[k] = v;
} else if (k in props) {
props[k] = v;
} else if (setupState && k in setupState) {
setupState[k] = v;
} else {
console.error("不存在");
}
},
});
created && created.call(renderContext);
// 将组件的 render 函数调用包装到 effect 内
effect(
() => {
const subTree = render.call(renderContext, renderContext);
if (!instance.isMounted) {
beforeMount && beforeMount.call(renderContext);
patch(null, subTree, container, anchor);
instance.isMounted = true;
// mounted && mounted.call(renderContext);
instance.mounted &&
instance.mounted.forEach((hook) => hook.call(renderContext));
} else {
beforeUpdate && beforeUpdate.call(renderContext);
patch(instance.subTree, subTree, container, anchor);
updated && updated.call(renderContext);
}
instance.subTree = subTree;
},
{
scheduler: queueJob,
}
);
}
function resolveProps(options, propsData) {
const props = {};
const attrs = {};
for (const key in propsData) {
if (key in options || key.startsWith("on")) {
props[key] = propsData[key];
} else {
attrs[key] = propsData[key];
}
}
return [props, attrs];
}
/**
* 更新子节点
*/
function patchElement(n1, n2) {
const el = (n2.el = n1.el);
const oldProps = n1.props;
const newProps = n2.props;
for (const key in newProps) {
if (newProps[key] !== oldProps[key]) {
patchProps(el, key, oldProps[key], newProps[key]);
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
patchProps(el, key, oldProps[key], null);
}
}
patchChildren(n1, n2, el);
}
/**
* 抽离封装核心 Diff算法
*/
function diff(n1, n2, container) {
console.log("此处是核心的 Diff算法");
console.log(n1);
console.log(n2);
const newChildren = n2.children;
const oldChildren = n1.children;
let j = 0;
let oldVNode = oldChildren[j];
let newVNode = newChildren[j];
// 1.更新相同的前置节点
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container);
j++;
oldVNode = oldChildren[j];
newVNode = newChildren[j];
}
let oldEnd = oldChildren.length - 1;
let newEnd = newChildren.length - 1;
oldVNode = oldChildren[oldEnd];
newVNode = newChildren[newEnd];
// 2.更新相同的后置节点
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container);
oldEnd--;
newEnd--;
oldVNode = oldChildren[oldEnd];
newVNode = newChildren[newEnd];
}
// 3.处理新增节点
if (j > oldEnd && j <= newEnd) {
const anchorIndex = newEnd + 1;
const anchor =
anchorIndex < newChildren.length
? newChildren[anchorIndex].el
: null;
while (j <= newEnd) {
patch(null, newChildren[j++], anchor);
}
} else if (j > newEnd && j <= oldEnd) {
// 4.处理移除节点
while (j <= oldEnd) {
unmount(oldChildren[j++]);
}
} else {
// 5.判断是否需要进行DOM移动
const count = newEnd - j + 1;
const source = new Array(count).fill(-1);
const oldStart = j;
const newStart = j;
// 如何判断节点是否需要移动
let moved = false;
let pos = 0;
// 【血战到底!】
// 构建索引表
const keyIndex = {};
for (let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i;
}
// 新增 patched 变量,代表更新过的节点数量
let patched = 0;
for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i];
// for(let k = newStart; k <= newEnd; k++) {
// const newVNode = newChildren[k]
// if (oldVNode.key === newVNode.key) {
// patch(oldVNode, newVNode, container)
// source[k - newStart] = i
// }
// }
if (patched < count) {
const k = keyIndex[oldVNode.key];
if (typeof k !== "undefined") {
newVNode = newChildren[k];
patch(oldVNode, newVNode, container);
patched++;
// 此处是关键!构造出来这么个玩意到底有什么用呢?
source[k - newStart] = i;
// 判断节点是否需要移动
if (k < pos) {
moved = true;
} else {
pos = k;
}
} else {
unmount(oldVNode);
}
} else {
unmount(oldVNode);
}
}
if (moved) {
// 如果 moved 为真,则需要进行 DOM 移动操作
const seq = getSequence(source);
console.log("seq", seq);
let s = seq.length - 1;
let i = count - 1;
for (i; i >= 0; i--) {
if (source[i] === -1) {
// 新增节点
const pos = i + newStart;
const newVNode = newChildren[pos];
const nextPos = pos + 1;
// 锚点
const anchor =
nextPos < newChildren.length
? newChildren[nextPos].el
: null;
// 挂载
patch(null, newVNode, container, anchor);
} else if (i !== seq[s]) {
// 该节点需要移动
const pos = i + newStart;
const newVNode = newChildren[pos];
const nextPos = pos + 1;
// 锚点
const anchor =
nextPos < newChildren.length
? newChildren[nextPos].el
: null;
console.log("newVNode.el", newVNode);
// 移动
insert(newVNode.el, container, anchor);
} else {
// 该节点不需要移动
s--;
}
}
}
}
}
/**
* 经典算法:最长递增子序列
*/
function getSequence(arr) {
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {
j = result[result.length - 1];
if (arr[j] < arrI) {
p[i] = j;
result.push(i);
continue;
}
u = 0;
v = result.length - 1;
while (u < v) {
c = ((u + v) / 2) | 0;
if (arr[result[c]] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1];
}
result[u] = i;
}
}
}
u = result.length;
v = result[u - 1];
while (u-- > 0) {
result[u] = v;
v = p[v];
}
return result;
}
/**
* 更新children
*/
function patchChildren(n1, n2, container) {
if (typeof n2.children === "string") {
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
}
setElementText(container, n2.children);
} else if (Array.isArray(n2.children)) {
if (Array.isArray(n1.children)) {
// 此处是核心的 Diff算法
diff(n1, n2, container);
} else {
setElementText(container, "");
// 挂载新节点
n2.children.forEach((c) => patch(null, c, container));
}
} else {
// 新子节点不存在,则卸载旧节点
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
} else if (typeof n1.children === "string") {
setElementText(container, "");
}
}
}
// 主要改动这里
function mountElement(vnode, container, anchor) {
const el = (vnode.el = createElement(vnode.type));
if (typeof vnode.children === "string") {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
vnode.children.forEach((child) => {
patch(null, child, el);
});
}
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
patchProps(el, key, null, value);
}
}
insert(el, container, anchor);
}
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
return false;
}
return key in el;
}
return { render };
}
/**
* 公共函数
*/
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
console.log("el", el);
return false;
}
return key in el;
}
// 测试代码
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor);
},
createText(text) {
return document.createTextNode(text);
},
setText(el, text) {
el.nodeValue = text;
},
patchProps(el, key, prevValue, nextValue) {
if (/^on/.test(key)) {
// const name = key.slice(2).toLowerCase();
// prevValue && el.removeEventListener(name, prevValue)
// el.addEventListener(name, nextValue);
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 绑定一个伪造的事件处理函数invoker
invoker = el._vei[key] = (e) => {
// e.timeStamp 触发事件的时间
if (e.timeStamp < invoker.attached) {
return;
}
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
invoker.value = nextValue;
// 绑定事件的时间
invoker.attached = performance.now();
el.addEventListener(name, invoker);
} else {
invoker.value = nextValue;
}
} else {
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, nextValue);
}
},
});
// 组件
const MyComponent = {
name: "MyComponent",
props: {
title: String,
},
data() {
return {
foo: "hello world",
};
},
setup(props, { emit }) {
onMounted(() => {
console.log("mounted 1");
});
onMounted(() => {
console.log("mounted 2");
});
// emit('change', 1,2)
// return () => {
// return {
// type: "h1",
// children: "Hello setup",
// props: {
// onclick: () => {
// emit("change", 1, 2);
// },
// },
// };
// };
const count = ref(10);
return {
count,
};
},
render() {
// 返回虚拟 DOM
// return {
// type: "div",
// children: `foo 的值是:${this.foo}, title is: ${this.title}, count is: ${this.count}`,
// };
return {
type: "div",
children: [
{
type: "header",
children: [this.$slots.header()],
},
{
type: "div",
children: [this.$slots.body()],
},
{
type: "footer",
children: [this.$slots.footer()],
},
],
};
},
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
};
const vnode1 = {
type: MyComponent,
props: {
title: "A big Title",
onChange: (...payload) => {
console.log("payload", payload);
alert("handle emit");
},
},
children: {
header() {
return { type: "h1", children: "我是标题" };
},
body() {
return { type: "section", children: "我是内容" };
},
footer() {
return { type: "p", children: "我是注脚" };
},
},
};
// const vnode2 = {
// type: MyComponent,
// props: {
// title: "A small Title",
// },
// };
renderer.render(vnode1, document.querySelector("#app"));
// setTimeout(() => {
// renderer.render(vnode2, document.querySelector("#app"));
// }, 2000);
</script>
</body>
</html>