手写MVVM框架-渲染v-for列表(修改List)
上一章我们实现了初始渲染时渲染了v-for列表,这一章我们来实现修改list后重新渲染list
实现步骤:
1.收集依赖时添加上对虚拟节点的依赖收集(前提)
2.从Object.defineProperty中劫持到数据变化调用重新渲染的rebuild方法
3.rebuild实现:先获取模板关联的依赖节点,遍历每一个节点(虚拟节点),清空虚拟节点父级(UL)中的html,然后把当前虚拟节点的dom重新添加到当前虚拟节点的父节点(UL), 此时因为数据发生了变化,所以虚拟节点下挂载的真实节点失效,根据这个真实节点(LI)重新虚拟节点,构建完成后,把当前的虚拟节点添加到父节点UL的children中
4.因为list发生了变化并且重新生成了虚拟节点,所以之前收集的依赖失效,需要清空依赖重新调用prepareRender方法
4.依赖收集完成之后,重新渲染当前虚拟节点
代码实现:
/**
* 代理执行数组方法
* @param {*} obj
* @param {*} func
* @param {*} namespace
*/
function defineProxyMethod(obj, func, namespace) {
const vm = this
Object.defineProperty(obj, func, {
configurable: true, // 可配置
enumerable: true, // 可枚举
value: function() {
// 获取执行方法
let method = arrayProto[func]
// 执行数组方法获取返回结果
let result = method.call(this, ...arguments)
// 重新收集依赖渲染节点
rebuild(vm, getNameSpace(namespace, ''))
return result; // 返回执行结果
}
})
}
export function prepareRender(vm, vnode) {
if(vnode == null) {
return null;
}
// 如果是文本节点
if(vnode.nodeType == 3) {
// 解析文本节点,并记录模板与节点之间的关系
analysisTemplateString(vnode);
}
// 如果是元素节点
if(vnode.nodeType == 1) {
analysisAttr(vm, vnode);
}
// 收集虚拟节点的依赖
if (vnode.nodeType == 0) {
setTemplateToVnode("{
{" + vnode.data + "}}", vnode);
setVnodeToTemplate("{
{" + vnode.data + "}}", vnode);
}
// 循环遍历子节点
for (let i = 0 ; i < vnode.children.length ; i ++) {
prepareRender(vm, vnode.children[i]);
}
}
export function rebuild(vm, data) {
// 获取当前data关联的vnode数据
const nodes = templateToNode.get(data)
nodes && nodes.forEach((node)=> {
// 清空父级的innerHTML
node.parent.ele.innerHTML = ""
// 重新设置父级的html
node.parent.ele.appendChild(node.ele)
// 重新渲染节点
const vnode = constructVnode(vm, node.ele, node.parent)
// 给父级(UL)添加children
node.parent.children = [vnode]
// 清空失效的依赖
clearMap()
// 重新收集依赖
prepareRender(vm, vm._vnode)
// 重新渲染节点
renderNode(vm, vnode)
})
}
我们在浏览器上或者在页面上添加一个事件向list中追加一个元素看效果