Vue3实现原理
来源于b站视频
模板渲染
<html>
<body>
<div id="app"></div>
</body>
</html>
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>
<script>
function h(tag, props, children) {
return {
tag,
props,
children,
};
}
function mount(vnode, container) {
const el = document.createElement(vnode.tag);
vnode.el = el;
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
el.setAttribute(key, value);
}
}
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach((child) => {
mount(child, el);
});
}
}
container.appendChild(el);
}
const vdom = h("div", { class: "red" }, [
h('span', null, 'hello')
]);
mount(vdom, document.getElementById("app"));
</script>
diff patch
在上面的render的基础上,进行patch
/**
* 四个假设
*/
function patch(n1, n2) {
if(!n1) {
mount(n2, document.getElementById('app'));
return;
}
//标签相同
if (n1.tag === n2.tag) {
const el = (n2.el = n1.el);
//props
const oldProps = n1.props || {};
const newProps = n2.props || {};
for (const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (newValue !== oldValue) {
el.setAttribute(key, newValue);
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
el.removeAttribute(key);
}
}
const oldChildren = n1.children;
const newChildren = n2.children;
// 子节点是文本节点
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren;
}
} else {
el.textContent = newChildren;
}
} else {
let commonLength = Math.min(newChildren.length, oldChildren?.length);
for (let i =0; i< commonLength; i++) {
patch(oldChildren[i], newChildren[i]);
}
if(newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach(child =>{
mount(child, el);
})
}else if (oldChildren.length > newChildren.length ){
oldChildren.slice(newChildren.length).forEach(child =>{
el.removeChild(child.el);
})
}
}
}
}
patch(null, vdom);
const vdom2 = h("div", { class: "green" }, [h("span", null, "world")]);
setTimeout(()=>{
patch(vdom, vdom2)
}, 500)
</script>
dep
<script>
let activeEffect = null;
class Dep {
constructor(value) {
this.subscribers = new Set();
this._value = value
}
get value(){
this.depend();
return this._value;
}
set value(val) {
this._value = val;
this.notify();
}
depend() {
if(activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect =>{
effect();
})
}
}
function watchEffect(effect){
activeEffect = effect;
effect();
activeEffect = null;
}
const dep = new Dep('hello');
watchEffect(() =>{
console.log(dep.value);
})
dep.value = 'change';
dep.notify(); // effect run
</script>
在上面的Dep基础上,再实现reactive,
reactive
<script>
let activeEffect = null;
class Dep {
subscribers = new Set();
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
}
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
let depsMap = new WeakMap();
const getDep = (target, key) => {
let depsForTarget = depsMap.get(target);
if (!depsForTarget) {
depsForTarget = new Map();
depsMap.set(target, depsForTarget);
}
let dep = depsForTarget.get(key);
if (!dep) {
dep = new Dep();
depsForTarget.set(key, dep);
}
return dep;
};
const reactiveHandler = {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend();
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
const dep = getDep(target, key);
dep.notify();
return result;
},
};
function reactive(raw) {
return new Proxy(raw, reactiveHandler);
}
let state = reactive({
count: 0,
});
watchEffect(() => {
// console.log(state.count);
// console.log(state.msg);
console.log('msg' in state);
});
state.count = 1;
</script>
已经有了响应式构建,依赖收集,副作用处理,节点渲染,下面就是实现一个小型vue了
mini-vue
首先在mount处理节点props时,加一个事件的判断for (const key in vnode.props) {
const value = vnode.props[key];
if(key.startsWith('on')){
el.addEventListener(key.slice(2).toLowerCase(), value)
}else {
el.setAttribute(key, value);
}
//创建一个组件
const App = {
data: reactive({ count: 0 }),
render() {
return h("div",{
onClick: ()=>{
this.data.count++
}
}, String(this.data.count));
},
};
function mountApp(component, container) {
let prevVdom;
let isMounted = false;
watchEffect(()=>{
if(!isMounted) {
prevVdom = component.render();
mount(prevVdom, container);
isMounted = true;
}else {
const newVdom = component.render();
patch(prevVdom, newVdom);
}
})
}
mountApp(App, document.getElementById('app'));
ok, that’s all