<div id="app">{{title}}</div>
<script>
function h(tag, props, children) {
return {
tag,
props,
children
}
}
// 填充代码
// 1. 基本结构
const Vue = {
// 拓展性
createRenderer({
querySelector,
insert,
createElement,
remove
}) {
// 返回渲染器
return {
createApp(options) {
// 返回app对象
return {
mount(selector) {
// 1. 找到宿主元素
const parent = querySelector(selector)
// 2. 渲染页面
if (!options.render) {
// 2.1 处理template: 编译
options.render = this.compile(parent.innerHTML)
}
// 收集 setup 和其他选项
if (options.setup) {
this.setupState = options.setup()
}
if (options.data) {
this.data = options.data()
}
// 渲染之前,处理setup和其他选项的兼容
const proxy = new Proxy(this, {
get(target, key) {
// 先从setup中取,如果取不到再从data中取
// 如果setup存在,且key在setup中定义过
if (target.setupState && key in target.setupState) {
// return target.setupState[key]
return Reflect.get(target.setupState, key)
} else {
// return target.data[key]
return Reflect.get(target.data, key)
}
},
set(target, key, val) {
if (target.setupState && key in target.setupState) {
return Reflect.set(target.setupState, key, val)
} else {
return Reflect.set(target.data, key, val)
}
}
})
this.update = effect(() => {
const vnode = options.render.call(proxy)
// 转换vnode为dom
// 初始化创建整棵树
if (!this.isMounted) {
// 实现 createElm, 整体创建,vnode -> el
const el = this.createElm(vnode)
parent.innerHTML = ''
insert(el, parent)
// init 初始化,设置已挂载标识
this.isMounted = true
} else {
this.patch(this._vnode, vnode)
}
this._vnode = vnode
})
},
patch(oldNode, newNode) {
const el = newNode.el = oldNode.el
// 1. 更新:必须更新相同节点
// 什么样的是相同节点
if (oldNode.tag === newNode.tag && oldNode.key === newNode.key) {
// update 相同节点,更新
const oldChild = oldNode.children
const newChild = newNode.children
if (typeof oldChild === 'string') {
if (typeof newChild === 'string') {
// 文本更新
if (oldChild !== newChild) {
el.textContent = newChild
}
} else {
// 替换文本为一组子元素,清空再创建并追加
el.textContent = ''
newChild.forEach(child => insert(this.createElm(child), el))
}
} else {
if (typeof newChild === 'string') {
// 替换一组子元素为文本
el.textContent = newChild
} else {
// updateChildren 比较两组子元素
this.updateChildren(el, oldChild, newChild)
}
}
} else {
// replace 不相同节点,替换
}
},
updateChildren(el, oldChild, newChild) {
// 1.获取newCh和oldCh中较短的那一个
const len = Math.min(oldChild.length, newChild.length)
// 强制更新
for (let i = 0; i < len; i++) {
this.patch(oldChild[i], newChild[i])
}
// 处理剩余元素
// 新数组元素多
if (newChild.length > oldChild.length) {
// 批量创建并追加
// 截取newCh中len后面的部分
newChild.slice(len).forEach(child => {
insert(this.createElm(child), el)
})
} else if (newChild.length < oldChild.length) {
// 批量删除
oldChild.slice(len).forEach(child => {
remove(child.el, el)
})
}
},
createElm(vnode) {
const {
tag,
props,
children
} = vnode
// 遍历vnode,创建整棵树
const el = createElement(tag)
// 递归
// 判断children是字符串
if (typeof children === 'string') {
el.textContent = children
} else {
children.forEach(child => insert(this.createElm(child), el))
}
// vnode中要保存真实DOM,以备未来更新使用
vnode.el = el
return el
},
compile(template) {
// 返回一个render函数
// parse -> ast
// generate: ast -> render
return function render() {
if (Array.isArray(this.title)) {
return h('h3', null, this.title.map(s => h('p', null, s)))
} else {
return h('h3', null, this.title)
}
// const h3 = document.createElement('h3')
// h3.textContent = this.title
// return h3
// 应该产生虚拟DOM
// return h('h3', null, this.title)
// return h('h3', null, [
// h('p', null, this.title),
// h('p', null, this.title),
// h('p', null, this.title)
// ])
}
},
}
}
}
},
createApp(options) {
// 创建一个web平台特有的渲染器
const renderer = Vue.createRenderer({
querySelector(sel) {
return document.querySelector(sel)
},
insert(el, parent) {
parent.appendChild(el)
},
createElement(tag) {
return document.createElement(tag)
},
remove(el, parent) {
parent.removeChild(el)
}
})
return renderer.createApp(options)
}
}
</script>
<script>
// 内容拦截用户对代理对象的访问,从而在值发生变化的时候做出响应
function reactive(obj) {
// 返回代理的对象
return new Proxy(obj, {
get(target, key) {
console.log('get key:', key)
track(target, key)
return Reflect.get(target, key)
},
set(target, key, val) {
console.log('set key:', key)
Reflect.set(target, key, val)
trigger(target, key)
}
})
}
// 建立映射关系: 依赖 Dep -> 组件更新函数
// 调用effect,首先执行fn
// 临时存储副作用函数 effectStack
const effectStack = []
function effect(fn) {
// 1. 执行一次fn
// fn()
const eff = function () {
try {
effectStack.push(eff)
fn()
} finally {
effectStack.pop()
}
}
// 立即调用一次
eff()
// 将这个函数返回出去
return eff
}
// Vue3: 创建map结构 { target: {key: [update1, update2]} }
const targetMap = {
// 应该往里面存这样的数据,依赖关系
// state: {
// 'title': [update]
// }
}
// 建立 target,key 和 effectStack 中存储的副作用函数之间的关系
function track(target, key) {
// 拿出存储副作用函数的最后一个元素
const effect = effectStack[effectStack.length - 1]
// 写死的话是这样的,但是不能这样写,要不然每次都新创建一个对象
// targetMap[target] = {}
// targetMap[target][key] = [effect]
// 所以应该先判断target为key的对象存不存在
let map = targetMap[target]
if (!map) {
// 首次get这个target【不存在就给map初始化一下】
map = targetMap[target] = {}
}
let deps = map[key]
if (!deps) {
deps = map[key] = []
}
// 映射关系建立
if (deps.indexOf(effect) === -1) {
deps.push(effect)
}
}
function trigger(target, key) {
const map = targetMap[target]
if (map) {
const deps = map[key]
if (deps) {
deps.forEach(dep => dep())
}
}
}
</script>
<script>
const app = Vue.createApp({
setup() {
const state = reactive({
title: 'hello, vue3 YK菌'.split("")
})
setTimeout(() => {
state.title = '2秒后见到新的YK菌'.split("")
}, 2000)
return state
}
})
app.mount('#app')
</script>