上一章示例代码

  1. function createRenderer(options) {
  2. const { createElement, setElementText, insert } = options
  3. //挂载函数
  4. function mountElement(vnode, container) {
  5. const el = createElement(vnode.type)
  6. if (typeof vnode.children === "string") {
  7. setElementText(el, vnode.children)
  8. }
  9. insert(el, container)
  10. }
  11. //n1旧vnode n2新vnode
  12. function patch(n1, n2, container) {
  13. if (!n1) {
  14. //n1为空说明是首次挂载
  15. mountElement(n2, container)
  16. } else {
  17. //...
  18. }
  19. }
  20. function render(vnode, container) {
  21. if (vnode) {
  22. //如果vnode存在,将其与旧vnode一起传递给patch,进行打补丁
  23. patch(container._vnode, vnode, container)
  24. } else {
  25. //如果不存在,说明是卸载操作
  26. container.innerHTML = ""
  27. }
  28. //保存vnode为旧vnode
  29. container._vnode = vnode
  30. //...
  31. }
  32. return {
  33. render,
  34. }
  35. }
  36. const renderer = createRenderer({
  37. createElement(tag) {
  38. return document.createElement(tag)
  39. },
  40. setElementText(el, text) {
  41. el.textContent = text
  42. },
  43. insert(el, parent, anchor = null) {
  44. parent.insertBefore(el, anchor)
  45. },
  46. })
  47. //首次渲染
  48. renderer.render(vnode, document.querySelector("#app"))

在上一章只实现了子节点是文本的插入,但vnode的子节点一般也是vnode而且通常有多个。

  1. const vnode = {
  2. type :'div',
  3. children:[
  4. {
  5. type:'p',
  6. children:'hello'
  7. }
  8. ]
  9. }

为了实现子节点的渲染我们需要修改mountElement函数:

  1. //挂载函数
  2. function mountElement(vnode, container) {
  3. const el = createElement(vnode.type)
  4. //为元素添加属性
  5. if(vnode.props){
  6. for(key in vnode.props){
  7. el.setAttribute(key,vnode.props[key])
  8. }
  9. }
  10. if (typeof vnode.children === "string") {
  11. setElementText(el, vnode.children)
  12. } else if (Array.isArray(vnode.children)) {
  13. //如果子节点是数组,则遍历每一个节点并且使用patch挂载它们
  14. vnode.children.forEach((child) => {
  15. patch(null, child, el)
  16. })
  17. }
  18. insert(el, container)
  19. }

HTML Attribute 与DOM Properties

HTML Attribute 指的是定义在HTML标签上的属性,例如

  1. <input id="my-input" value="foo" type="text"/>

这里指的就是id=”my-input” value=”foo” type=”text”。当浏览器解析这段HTML代码后,会创建一个与之相符的DOM元素对象,可以通过JavaScript来读取这个DOM对象

  1. const input = document.querySelector('#my-input')

image.png
可以看到这个DOM有很多自己的属性。
HTMl Attribute与DOm Properties的关系很复杂,但其实我们只需要记住一个核心原则:HTML Attribute的作用是设置对应DOM Properties的初始值。

节点更新

  1. //更新节点
  2. function patchElement(n1, n2) {
  3. const el = (n2.el = n1.el)
  4. const oldProps = n1.props
  5. const newProps = n2.props
  6. //更新prop
  7. for (key in newProps) {
  8. if (oldProps[key] !== newProps[key]) {
  9. patchProps(el, key, oldProps[key], newProps[key])
  10. }
  11. }
  12. for (key in oldProps) {
  13. if (!key in newProps) {
  14. patchProps(el, key, oldProps[key], null)
  15. }
  16. }
  17. //更新children
  18. patchChildren(n1, n2, el)
  19. }
  20. //更新子节点
  21. function patchChildren(n1, n2, el) {
  22. //新子节点是文本
  23. if (typeof n2.children === "string") {
  24. //新子节点如果是数组则逐个挂载
  25. if (Array.isArray(n1.children)) {
  26. n1.children.forEach((child) => {
  27. unmount(child)
  28. })
  29. }
  30. //设为文本
  31. setElementText(el, n2.children)
  32. } else if (Array.isArray(n2.children)) {
  33. //新节点是数组
  34. if (Array.isArray(n1.children)) {
  35. //如果旧几点是数组要进行diff
  36. } else {
  37. setElementText(el, "")
  38. n2.children.forEach((child) => {
  39. patch(null, child, el)
  40. })
  41. }
  42. } else {
  43. //新节点为空
  44. if (Array.isArray(n1.children)) {
  45. n1.children.forEach((child) => {
  46. unmount(child)
  47. })
  48. } else {
  49. setElementText(el, "")
  50. }
  51. }
  52. }
  53. //n1旧vnode n2新vnode
  54. function patch(n1, n2, container) {
  55. if (n1 && n1.type != n2.type) {
  56. unmount(n1)
  57. n1 = null
  58. }
  59. const type = typeof n2.type
  60. if (type === "string") {
  61. if (!n1) {
  62. //n1为空说明是首次挂载
  63. mountElement(n2, container)
  64. } else {
  65. patchElement(n1, n2)
  66. }
  67. }
  68. }