前言
最开始看到 vanjs
的时候,也是很震惊,短短100多行代码就实现了一个框架。阅读源码时候,越来越震惊,这么点代码连 state
、 derive(类 computed)
也实现了,而且实现得十分巧妙。
除了代码风格读起来实在是难受,bind(() => (setter(v.val), dom))
就这一句就卡了我好久,一直以为是逗号分隔参数,实际却是逗号语句,只是一个参数。
源码理解
由于总共就 100多行代码,就不贴源码了,简单介绍一下思路,有兴趣的大哥可以自己去看一看,逻辑并不是很复杂。
demo
首先,上一个比较简单的使用demo
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="module" src="./src/van.js"></script>
<style>
#div {
background: red;
color: green;
}
</style>
</head>
<body>
<script type="module">
import van from "./src/van.js";
const { button, div } = van.tags;
const counter = van.state(0)
const Counter = () => {
return div(
{
id: 'div',
style: () => `font-size: 16px;margin-top: ${counter.val}px`
},
[
"❤️ ", counter, " ",
button({onclick: () => ++counter.val}, "👍"),
button({onclick: () => --counter.val}, "👎"),
() => {
if (counter.val) {
return 1111
}
return 2222
}
]
)
}
van.add(document.body, Counter())
</script>
</body>
</html>
页面的展现如下图所示
API解析
dom
首先,可以看到demo中使用了 div()
button()
这样的函数来创建 dom
。
其实是使用 proxy
代理了 document.createElement
,
div() => document.createElement('div')
。
html
<script>
const tags = new Proxy((name) => document.createElement(name), {
get(target, name) {
return target.bind(undefined, name);
}
})
const { div } = tags;
console.log('div', div());
</script>
然后再来看看内部的参数,第一个参数代表 dom
的 props
,由于涉及到了 state
放到下一步细说,第二个参数以及后面的参数,就是表示 div
的 children
,创建 dom
的方法和现在一致,创建成功后,通过 add
函数,append
到父元素下。
children
也可以使用一个函数返回 dom
,类似于上面代码中涉及条件判断时候会使用到, van
会运行后进行依赖收集(如果有)后,返回 dom
。
props
第二个参数就是绑定在 dom
上的,props
,然后根据 val
的类型做了区分。
- 函数的话,运行后(函数中可能会带
state
,因此也要收集依赖)获取返回值。 - 直接是
state
的话,也封成一个函数用来依赖收集。 - 正常的值,直接返回。
最后返回的值也是通过 dom.setAttribute
修改 dom
属性。
state
一般来说,state
会用在 3 个方面。
- 直接用在
props
中{ key: state }
- 也是用在
props
中,但是通过函数返回{ key: () => state.val }
- 返回
dom
节点相关操作 ,() => (state ? dom1 : dom2 )
实现原理也很简单,类似于 vue 的依赖收集,在 getter
中收集依赖,然后将操作 state
的相关函数绑定,然后,修改时,触发绑定的函数,然后更新 dom
垃圾收集
因为要涉及新老 dom
的更新替换,所以原有的 dom
会被保存起来,但是我们知道就算 dom
卸载了,如果还存在引用,那这个 dom
节点也会继续存在,导致内存泄露, van
中也根据这个做了一个处理,通过
dom.isConnected
来判断节点是否还存在 document
中,进行释放。
let keepConnected = l => l.filter(b => b._dom?.isConnected)
结语
derive
部分的代码粗略看了看,基本和 state
是差不多的逻辑,多了一个 listeners
的绑定,原来应该也是一致的,通过依赖收集重新运行绑定的 fn
,然后 updateDom
就我个人而言,感觉最优秀的设计,就是通过 proxy
和 bind
把 document.createElement
实现成了div()
这样的语法,然后代码中也运用了大量的 bind
来绑定 this
和 固定参数,用并不是很复杂的代码实现了这个 麻雀虽小,五脏俱全 的框架。
上周起立了个每周写一篇笔记的 flag
,结果周末喝酒喝的醉生梦死,还是拖到了周一。=-,希望这礼拜能按时完成(flag + 1)。
最后的最后
帮大哥捞捞人,杭州。 大哥人很好 0.0,天天被我 diss 都没脾气。