vanjs源码笔记

2,486 阅读3分钟

vanjs官网

前言

最开始看到 vanjs 的时候,也是很震惊,短短100多行代码就实现了一个框架。阅读源码时候,越来越震惊,这么点代码连 state、 derive(类 computed) 也实现了,而且实现得十分巧妙。

除了代码风格读起来实在是难受,bind(() => (setter(v.val), dom)) 就这一句就卡了我好久,一直以为是逗号分隔参数,实际却是逗号语句,只是一个参数。

源码理解

由于总共就 100多行代码,就不贴源码了,简单介绍一下思路,有兴趣的大哥可以自己去看一看,逻辑并不是很复杂。

demo

首先,上一个比较简单的使用demo

<!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>

页面的展现如下图所示

image.png

API解析

dom

image.png

首先,可以看到demo中使用了 div() button() 这样的函数来创建 dom。

其实是使用 proxy 代理了 document.createElement,

div() => document.createElement('div')。

<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 的类型做了区分。

  1. 函数的话,运行后(函数中可能会带 state ,因此也要收集依赖)获取返回值。
  2. 直接是 state 的话,也封成一个函数用来依赖收集。
  3. 正常的值,直接返回。

最后返回的值也是通过 dom.setAttribute 修改 dom 属性。

state

一般来说,state 会用在 3 个方面。

  1. 直接用在 props 中 { key: state }
  2. 也是用在 props 中,但是通过函数返回 { key: () => state.val }
  3. 返回 dom 节点相关操作 , () => (state ? dom1 : dom2 )

实现原理也很简单,类似于 vue 的依赖收集,在 getter 中收集依赖,然后将操作 state 的相关函数绑定,然后,修改时,触发绑定的函数,然后更新 dom

image.png

垃圾收集

因为要涉及新老 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)。

最后的最后

image.png 帮大哥捞捞人,杭州。 大哥人很好 0.0,天天被我 diss 都没脾气。