在上一篇文章的基础上添加处理函数组件渲染。

1、改造index.js为函数组件

  1. // index
  2. import React from './react';
  3. import ReactDOM from './react-dom';
  4. function FunctionComponent(props) {
  5. return (
  6. <div className="title" style={{ background: 'red' }}>
  7. <span>{props.name}</span>
  8. </div>
  9. )
  10. }
  11. ReactDOM.render(<FunctionComponent name="xiaoming" />,
  12. document.getElementById('root')
  13. );

2、添加处理函数组件

createDom函数增加处理函数组件逻辑:
image.png

mountFunctionComponent函数实现:实现逻辑其实很简单,就是调用函数组件,传入props,使其成为原生组件,重复调用原生组件逻辑。createElement
image.png

3、实现效果

image.png

4、完整代码

  1. // react-dom.js
  2. /**
  3. * createElement编译出来的结果
  4. * {"type":"div","key":null,"ref":null,
  5. * "props":{"className":"title","style":{"color":"red"},
  6. * "children":{"type":"span","key":null,
  7. * "ref":null,"props":{"children":"hello word"}
  8. */
  9. /**
  10. * 1.把虚拟DOM变为真实DOM;
  11. * 2.把虚拟DOM属性同步到真实DOM上;
  12. * 3.把虚拟DOM上的children也变成真实DOM挂载到自己的DOM上;
  13. * 4.把最终DOM挂载到容器上;
  14. * @param {*} vdom 要渲染的虚拟DOM
  15. * @param {*} container 虚拟DOM转换成真实DOM,并插入到容器中
  16. */
  17. function render(vdom, container) {
  18. const dom = createDom(vdom)
  19. container.appendChild(dom)
  20. }
  21. /**
  22. * 虚拟dom变为真实dom
  23. * @param {*} vdom 虚拟dom
  24. */
  25. function createDom(vdom) {
  26. // 如果是字符串或者数字,直接返回真实节点
  27. if (['number', 'string'].includes(typeof vdom)) {
  28. return document.createTextNode(vdom)
  29. }
  30. // 否则它就是一个react元素了
  31. const { type, props } = vdom
  32. let dom
  33. if (typeof type === 'function') {
  34. // 函数组件直接返回
  35. return dom = mountFunctionComponent(vdom)
  36. } else {
  37. // 原生组件
  38. dom = document.createElement(type)
  39. }
  40. // 将虚拟dom属性更新到真实dom上
  41. updateProps(dom, props)
  42. // 如果儿子只有一个儿子,并且儿子是文本节点
  43. if (['number', 'string'].includes(typeof props.children)) {
  44. dom.textContent = props.children
  45. // 如果儿子是一个对象,并且是一个虚拟dom,递归调用render
  46. } else if (typeof props.children === 'object' && props.children.type) {
  47. // 把儿子变成真实dom,插入到自己身上
  48. render(props.children, dom)
  49. // 如果儿子是一个数组,说明儿子不止一个
  50. } else if (Array.isArray) {
  51. reconcileChildren(props.children, dom)
  52. } else {
  53. document.textContent = props.children ? props.children.toString() : ""
  54. }
  55. // // 把真实dom作为一个虚拟属性放在虚拟dom上,为以后更新做准备
  56. // vdom.dom = dom
  57. return dom
  58. }
  59. /**
  60. * @param {*} childrenVdom 儿子们的虚拟dom
  61. * @param {*} parentDom 父的真实dom
  62. */
  63. function reconcileChildren(childrenVdom, parentDom) {
  64. for (const childVdom of childrenVdom) {
  65. render(childVdom, parentDom)
  66. }
  67. }
  68. /**
  69. * 把一个类型为自定义函数组件的虚拟dom转换为真实dom并返回
  70. * @param {*} vdom 自定义函数虚拟dom
  71. */
  72. function mountFunctionComponent(vdom) {
  73. const { type:FunctionComponent, props } = vdom
  74. // 直接调用
  75. let renderVdom = FunctionComponent(props)
  76. return createDom(renderVdom)
  77. }
  78. /**
  79. * 将虚拟dom属性更新到真实dom上
  80. * @param {*} dom 真实dom
  81. * @param {*} newProps 新属性
  82. */
  83. function updateProps(dom, newProps) {
  84. for (const key in newProps) {
  85. if (key === 'children') continue
  86. // style单独处理,因为dom不支持
  87. if (key === 'style') {
  88. const styleObj = newProps.style
  89. for (let attr in styleObj) {
  90. dom.style[attr] = styleObj[attr]
  91. }
  92. } else {
  93. dom[key] = newProps[key]
  94. }
  95. }
  96. }
  97. const ReactDom = { render }
  98. export default ReactDom

5、源代码

本文代码:https://gitee.com/linhexs/react-write/tree/2.react-FunctionComponent-render/