实现自己的virtual dom
通过实现一个短小的 virtual dom,了解 virtual dom 的原理,进而加深对 React 的理解。项目地址
今天要实现的功能如下:
1 | //定义一个组件 |
看起来是不是很像 react?
- 支持自定义组价(组件可互相嵌套)
- 事件
- 支持 attribute
- 组件的 diff 更新
下面我们来一步步实现这些功能。
使用过 jsx 的同学对它肯定很喜欢
1 | const wrapper = ( |
如果想使用 jsx 语法,我们可以借助于 babel。babel 在解析 jsx 的时候需要指定一个函数,如果你写过 react 的话,那默认是 React.createElement 函数。这里我们这里定义一个函数 create:
1 | function create(type, attributes, ...children) { |
有个这个函数 wrapper, babel 在解析 wrapper 的时候会依次传入 type、attributes、child1、child2….
下边的写法完全等价于上边的 jsx 语法:
1 | const wrapper = create( |
创建
现在我们需要把 wrapper 转换为真正的 dom,我们定义一个方法 createElement:
1 | /*** |
createElement 方法会将 vnode 转换为真实的 dom,但是目前这个方法只能处理 Element 类型的元素,文本元素都无法处理。我们先看下有多少个类型需要处理。
- Element 类型(native)
- Text 类型(text)
- 自定义组件类型, 如 MyButton(thunk)
- 空类型(empty)在 react 中会生成 noscript。{undefined}就会生成一个空类型,空类型的作用在与站位,便于 diff 算法优化。
所以我们来改写下 createElement:
1 | /*** |
写到这里,可能有些人会不知道 vnode 和 vnode 的 type 属性从哪里来的,这里我们回过头来重写下 create 函数:
1 | function create(type, attributes, ...children){ |
更新
dom 创建完成之后,就需要更新 dom 操作了。其实 dom 的更新可以抽象出一下几点
- 添加(appendChild),增加了新的元素
- 删除 (removeChild),删除了元素
- 取代 (replaceChild),元素类型不同时元素取代
- 更新(diffAttribute, diffChildren),元素类型相同,则比较 attribute 和 children
1 | /** |
文章中的代码大多参考了这里: