尤雨溪-写一个mini vue
<div id="app"></div>
<script>
function h(tag, props, children) {
return {
tag,
props,
children
}
}
function mount(vnode, container) { // 参数: (h('span',null,'hello'), '#div')
const el = document.createElement(vnode.tag)
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key]
el.setAttribute(key, value)
}
}
if (vnode.children) {
// content
if (typeof vnode.children === 'string') {
el.textContent = vnode.children
} else {
console.log(vnode.children)
// tag
vnode.children.forEach(child => {
mount(child, el)
})
}
}
container.appendChild(el)
}
function patch(n1, n2) {
// patch, 当两个都是div标签的时候
const el = n1.el = n2.el
if (n1.tag == n2.tag) {
const oldProps = n1.props || {}
const newProps = n2.props || {}
// props设置为新节点的属性, 当两个props不相等的时候 1.1
for (const key in newProps) {
const oldValue = oldProps[key]
const newValue = newProps[key]
if (oldValue !== newValue) {
el.setAttribute(key, newValue)
}
}
// 删除props, 当只有旧节点中有props的时候 1.2
for (const key in oldProps) {
if (!(key in newProps)) {
el.removeAttribute(key)
}
}
const oldChildren = n1.children
const newChildren = n2.children
//textcontent直接为新节点的值, 当新节点没有子节点的时候 2.1
if (typeof newChildren == 'string') {
if (typeof oldChildren == 'string') {
if (newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.textContent = newChildren
}
} else {
// 新节点的子节点挂载上去, 当只有新节点有子节点的时候 2.2
if (typeof oldChildren == 'string') {
el.innerHTML = ''
newChildren.forEach(item => {
mount(item, el)
})
} else {
//比较, 当两个都有子节点span的时候 2.3
const commonLength = Math.min(oldChildren.length, newChildren.length)
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i])
}
if (newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => { el.removeChild(item.el) })
}
if (newChildren.length > oldChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => { mount(item, el) })
}
}
}
} else {
// replace, 当两个dom不相同的时候
}
}
let activeEffect;
// 依赖收集器
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
}
const targetMap = new WeakMap()
function getTarget(target, key) {
// depsMap为Map类型 里面存储要响应的对象的Map
/**
* {
* [target]: Map()
*}
*/
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 为每一个key设置Dep监听类
let dep = depsMap.get(key)
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
return dep
}
const reactiveHandler = {
get(target, key, receiver) {
const dep = getTarget(target, key)
// 收集依赖
dep.depend()
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const dep = getTarget(target, key)
const result = Reflect.set(target, key, value, receiver)
dep.notify()
return result
}
}
function reactive(raw) {
return new Proxy(raw, reactiveHandler)
}
const App = {
data: reactive({
count: 0
}),
render() {
return h('div', {
onClick: () => {
this.data.count++
}
}, String(this.data.count))
}
}
function mountApp(component, container) {
let isMounted = false
let prevVdom
watchEffect(() => {
if (!isMounted) {
prevVdom = component.render()
mount(prevVdom, container)
isMounted = true
} else {
const newVdom = component.render()
patch(prevVdom, newVdom)
prevVdom = newVdom
}
})
}
mountApp(App, document.getElementById('app'))
</script>