欢迎访问成都祈钰瑶科技有限公司!

全国免费服务热线:

18982081108
✕

成都网站设计公司,成都响应式网站设计,成都移动端网站设计,成都网站UI设计

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

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

创新互联建站主要从事网站制作、网站设计、网页设计、企业做网站、公司建网站等业务。立足成都服务井冈山,10年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:028-86922220

一、是什么

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.  
  15. function FunctionComponent(props) { 
  16.   return ( 
  17.     <div className="border"> 
  18.       FunctionComponent 
  19.       <p>{props.name}</p> 
  20.     </div> 
  21.   ); 
  22. } 
  23.  
  24. const jsx = ( 
  25.   <div className="border"> 
  26.     <p>xx</p> 
  27.     <a href="https://www.xxx.com/">xxx</a> 
  28.     <FunctionComponent name="函数组件" /> 
  29.     <ClassComponent name="类组件" color="red" /> 
  30.   </div> 
  31. ); 

这些类别最终都会被转化成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. } 
  18. function createTextNode(text) { 
  19.     return { 
  20.         type: TEXT, 
  21.         props: { 
  22.             children: [], 
  23.             nodeValue: text 
  24.         } 
  25.     }; 
  26. } 
  27. export default { 
  28.     createElement 
  29. }; 

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

三、总结

在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