简易的Ref数据响应式+Renderer渲染器
本案例实现的响应式效果非常有限,只做简单的demo演示,本案例既不是基于Object.defineProperty
,也不是基于Proxy
实现的响应式,而是基于存取器,其实都差不多。
准备一个 __Ref__
,它可以将简单数据类型包装为一个响应式对象。
class __Ref {
#privateValue;
#privateRely; // "桶" 收集所有对#privateValue产生依赖的dom元素
constructor(value) {
this.#privateValue = value
this.#privateRely = []
}
pushPrivateRely(newRely) {
this.#privateRely.push(newRely)
}
get value() {
return this.#privateValue
}
set value(newValue) {
this.#privateValue = newValue
effect(this.#privateRely, newValue)
}
}
接着,准备一个副作用函数,用于将 #privateRely
中标记的dom节点逐一更新。effect
副作用函数的调用时机发生在ref
的值被改变时。
function effect(relyList, newValue) {
relyList.forEach(item => {
if (item.innerText) {
return item.node.innerText = newValue
}
if (item.prop) {
return item.node[prop] = newValue
}
})
}
接着是一个对外暴露的函数,它用于初始化一个响应式包装类。这里其实是想减少一个new关键字的写法而特意封装的一个工厂函数,这样只需要ref()
就行了。
export function ref(value) {
return new __Ref(value)
}
最后就是渲染器,它接收一个表示虚拟dom的结构对象和表示挂载点的dom元素。渲染器会遍历虚拟dom中的 props
,提取其中的事件和属性(这里并不兼容一些特定属性,例如 style,
class
)。接着对 children
进行分析,如果为 ref
响应式对象,则在该对象下的 #privateRely
依赖"桶"中标记当前dom节点进行响应式链接,否则直接渲染为一个文本节点。
export function renderer(vnode, container) {
const el = document.createElement(vnode.tag)
for (const key in vnode.props) {
// 提取事件
const onEvent = /^on/.test(key)
const eventName = key.substr(2).toLowerCase()
if (onEvent) {
el.addEventListener(eventName, vnode.props[key])
continue
}
// 提取props
el[key] = vnode.props[key].value
vnode.props[key].pushPrivateRely({
node: el,
[key]: true
})
}
// 提取children: 处理响应式数据
if (vnode.children instanceof __Ref) {
const children = document.createTextNode(vnode.children.value)
vnode.children.pushPrivateRely({
node: el,
innerText: true
})
el.appendChild(children)
}
// 处理children-vnodes
else if (vnode.children instanceof Array) {
vnode.children.forEach(child => renderer(child, el))
}
// 处理静态数据
else {
const children = document.createTextNode(vnode.children.toString())
el.appendChild(children)
}
// 挂载元素
container.appendChild(el)
}
最后一步(渲染器)已经完成了,现在解构出 ref
和 renderer
实现一个简单的渲染器并根据响应式数据渲染内容。
import { ref, renderer } from './myvue.js'
let navList = ['Home', 'News', 'Person']
let logoLabel = ref('Vue.js')
renderer({
tag: 'div',
children: [
{
tag: 'b',
children: logoLabel
},
...navList.map(
(el, i) => ({
tag: 'span',
children: el
})
)
]
}, document.querySelector("#app"))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。