面试官:说说React Jsx转换成真实DOM过程?

开发 前端
react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后 React会将这些「变化」更新到屏幕上。

[[414295]]

本文转载自微信公众号「JS每日一题」,作者灰灰。转载本文请联系JS每日一题公众号。

一、是什么

react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后 React会将这些「变化」更新到屏幕上

在前面文章了解中,JSX通过babel最终转化成React.createElement这种形式,例如:

  1. <div> 
  2.   <img src="avatar.png" className="profile" /> 
  3.   <Hello /> 
  4. </div> 

 

 

会被bebel转化成如下:

  1. React.createElement( 
  2.   "div"
  3.   null
  4.   React.createElement("img", { 
  5.     src: "avatar.png"
  6.     className: "profile" 
  7.   }), 
  8.   React.createElement(Hello, null
  9. ); 

在转化过程中,babel在编译时会判断 JSX 中组件的首字母:

  • 当首字母为小写时,其被认定为原生 DOM 标签,createElement 的第一个变量被编译为字符串
  • 当首字母为大写时,其被认定为自定义组件,createElement 的第一个变量被编译为对象

最终都会通过RenderDOM.render(...)方法进行挂载,如下:

  1. ReactDOM.render(<App />,  document.getElementById("root")); 

二、过程

在react中,节点大致可以分成四个类别:

  • 原生标签节点
  • 文本节点
  • 函数组件
  • 类组件

如下所示:

  1. class ClassComponent extends Component { 
  2.   static defaultProps = { 
  3.     color: "pink" 
  4.   }; 
  5.   render() { 
  6.     return ( 
  7.       <div className="border"
  8.         <h3>ClassComponent</h3> 
  9.         <p className={this.props.color}>{this.props.name}</p> 
  10.       </div> 
  11.     ); 
  12.   } 
  13.  
  14. function FunctionComponent(props) { 
  15.   return ( 
  16.     <div className="border"
  17.       FunctionComponent 
  18.       <p>{props.name}</p> 
  19.     </div> 
  20.   ); 
  21.  
  22. const jsx = ( 
  23.   <div className="border"
  24.     <p>xx</p> 
  25.     <a href="https://www.xxx.com/">xxx</a> 
  26.     <FunctionComponent name="函数组件" /> 
  27.     <ClassComponent name="类组件" color="red" /> 
  28.   </div> 
  29. ); 

这些类别最终都会被转化成React.createElement这种形式

React.createElement其被调用时会传?标签类型type,标签属性props及若干子元素children,作用是生成一个虚拟Dom对象,如下所示:

  1. function createElement(type, config, ...children) { 
  2.     if (config) { 
  3.         delete config.__self; 
  4.         delete config.__source; 
  5.     } 
  6.     // ! 源码中做了详细处理,⽐如过滤掉key、ref等 
  7.     const props = { 
  8.         ...config, 
  9.         children: children.map(child => 
  10.    typeof child === "object" ? child : createTextNode(child) 
  11.   ) 
  12.     }; 
  13.     return { 
  14.         type, 
  15.         props 
  16.     }; 
  17. function createTextNode(text) { 
  18.     return { 
  19.         type: TEXT, 
  20.         props: { 
  21.             children: [], 
  22.             nodeValue: text 
  23.         } 
  24.     }; 
  25. export default { 
  26.     createElement 
  27. }; 

createElement会根据传入的节点信息进行一个判断:

  • 如果是原生标签节点, type 是字符串,如div、span
  • 如果是文本节点, type就没有,这里是 TEXT
  • 如果是函数组件,type 是函数名
  • 如果是类组件,type 是类名

虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:

  1. ReactDOM.render(element, container[, callback]) 

当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 diff算法进行高效的更新

如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行

render大致实现方法如下:

  1. function render(vnode, container) { 
  2.     console.log("vnode", vnode); // 虚拟DOM对象 
  3.     // vnode _> node 
  4.     const node = createNode(vnode, container); 
  5.     container.appendChild(node); 
  6.  
  7. // 创建真实DOM节点 
  8. function createNode(vnode, parentNode) { 
  9.     let node = null
  10.     const {type, props} = vnode; 
  11.     if (type === TEXT) { 
  12.         node = document.createTextNode(""); 
  13.     } else if (typeof type === "string") { 
  14.         node = document.createElement(type); 
  15.     } else if (typeof type === "function") { 
  16.         node = type.isReactComponent 
  17.             ? updateClassComponent(vnode, parentNode) 
  18.         : updateFunctionComponent(vnode, parentNode); 
  19.     } else { 
  20.         node = document.createDocumentFragment(); 
  21.     } 
  22.     reconcileChildren(props.children, node); 
  23.     updateNode(node, props); 
  24.     return node; 
  25.  
  26. // 遍历下子vnode,然后把子vnode->真实DOM节点,再插入父node中 
  27. function reconcileChildren(children, node) { 
  28.     for (let i = 0; i < children.length; i++) { 
  29.         let child = children[i]; 
  30.         if (Array.isArray(child)) { 
  31.             for (let j = 0; j < child.length; j++) { 
  32.                 render(child[j], node); 
  33.             } 
  34.         } else { 
  35.             render(child, node); 
  36.         } 
  37.     } 
  38. function updateNode(node, nextVal) { 
  39.     Object.keys(nextVal) 
  40.         .filter(k => k !== "children"
  41.         .forEach(k => { 
  42.         if (k.slice(0, 2) === "on") { 
  43.             let eventName = k.slice(2).toLocaleLowerCase(); 
  44.             node.addEventListener(eventName, nextVal[k]); 
  45.         } else { 
  46.             node[k] = nextVal[k]; 
  47.         } 
  48.     }); 
  49.  
  50. // 返回真实dom节点 
  51. // 执行函数 
  52. function updateFunctionComponent(vnode, parentNode) { 
  53.     const {type, props} = vnode; 
  54.     let vvnode = type(props); 
  55.     const node = createNode(vvnode, parentNode); 
  56.     return node; 
  57.  
  58. // 返回真实dom节点 
  59. // 先实例化,再执行render函数 
  60. function updateClassComponent(vnode, parentNode) { 
  61.     const {type, props} = vnode; 
  62.     let cmp = new type(props); 
  63.     const vvnode = cmp.render(); 
  64.     const node = createNode(vvnode, parentNode); 
  65.     return node; 
  66. export default { 
  67.     render 
  68. }; 

三、总结

在react源码中,虚拟Dom转化成真实Dom整体流程如下图所示:

其渲染流程如下所示:

  • 使用React.createElement或JSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...) ,Babel帮助我们完成了这个转换的过程。
  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

参考文献

https://bbs.huaweicloud.com/blogs/265503)

https://huang-qing.github.io/react/2019/05/29/React-VirDom/

 

https://segmentfault.com/a/1190000018891454

 

责任编辑:武晓燕 来源: JS每日一题
相关推荐

2021-06-29 09:47:34

ReactSetState机制

2021-06-30 07:19:36

React事件机制

2021-08-02 08:34:20

React性能优化

2021-09-14 07:06:13

React项目TypeScript

2024-03-05 10:33:39

AOPSpring编程

2021-07-07 08:36:45

React应用场景

2021-08-03 07:51:43

React项目面试

2024-02-29 16:49:20

volatileJava并发编程

2023-12-27 18:16:39

MVCC隔离级别幻读

2021-05-19 08:40:36

DNS 协议查询

2021-08-03 08:41:18

SQLMysql面试

2021-07-13 07:52:03

ReactHooks组件

2021-08-05 08:32:27

React开发项目

2024-02-20 08:13:35

类加载引用Class

2024-03-28 10:37:44

IoC依赖注入依赖查找

2024-03-14 14:56:22

反射Java数据库连接

2021-11-25 10:18:42

RESTfulJava互联网

2024-03-11 18:18:58

项目Spring线程池

2020-07-02 07:52:11

RedisHash映射

2021-05-20 08:34:03

CDN原理网络
点赞
收藏

51CTO技术栈公众号