「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」
前面已经模仿vueAPI风格,把render的默认写在框架内部了,代码进行了模块分割。 现在,就来处理比较明显的问题。 那就是,我们目前的渲染函数,每次触发时,都是清空以前的,然后添加新的。但是,实际上并不是每个元素都需要更新,这样直接一棍子打死,不免有许多浪费。 我们耳熟能详的vdom diff就是解决这个问题的。
vnode的意义
简单回顾一下, 虚拟dom是真实dom在js对象里的体现。其作用就是方便我们进行diff, 因为直接从dom树里读取节点信息会比较慢,至于为什么比较慢,首先去dom上查找元素节点就不快,然后 , console.dir()就能看到一个原生节点上,那多如韭菜的属性,而且大部分,和我们要进行的diff操作是没有关系的。 我们只关心这个节点能否复用。
实现VNode
首先,得先实现一个vNode,才能用它去diff。我们先简单实现一下,一个节点它首先有节点类型,其次如果是元素节点必须得有tagName,然后就是元素的属性(就是我们直接写在标签上的,使用el.setAttribute()进行设置),然后它可能有子节点。 我们先来个最简单的,只考虑元素节点
export function h({tag, props, children}){
return {
tag,props, children
}
}
实现编译vnode
有了vNode我们先不考虑diff,先考虑怎么编译回去,自己引入的新问题自己得先解决。 上面的children是可以是一个Domstring 的,在只有一个文本节点的情况下(这种情况很多)就不使用数组了,这是由于我们的新的API append可以直接传入一个字符串,它会帮我们转为一个文本节点。
编译的基本流程就是,下面这些,而这些可以单独拆出来,为的是能够跨平台,如果不是dom,就让用户自定义。 。
graph TD
创建节点- --> 赋予节点属性- --> 将节点追加到父节点13
如果有children,就判断类型,字符串直接append,否则遍历调用 mountElement。
export function mountElement(vNode, rootContainer){
const { tag, children, props} = vNode ;
/* 创建 */
const el = createElement(tag)
// 设置属性
for (const [key, val] of Object.entries(props)){
patchProps(el, key,null, val )
}
/* insert */
if( typeof children === 'string'){
el.append(children)
}else if( Array.isArray(children)) {
mountChildren(children, el)
}
/*把元素装进去 */
insert(el,rootContainer)
}
至此,我们又把自己的vNode折腾回去了,看看新的写法能不能正常运行。
看上去没啥问题, 下一次就是期待的dom diff了,这些全都是准备工作