问答题704/1587说说React Jsx转换成真实DOM过程?

难度:
2021-10-24 创建

参考答案:

一、是什么

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

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

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

会被babel转化成如下:

1React.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(...)方法进行挂载,如下:

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

二、过程

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

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

如下所示:

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

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

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

1function 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} 18function createTextNode(text) { 19 return { 20 type: TEXT, 21 props: { 22 children: [], 23 nodeValue: text 24 } 25 }; 26} 27export default { 28 createElement 29};

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

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

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

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

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

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

render大致实现方法如下:

1function 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节点 9function 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中 29function 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} 41function 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// 执行函数 56function 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函数 65function 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} 72export 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

最近更新时间:2024-08-10

赞赏支持

预览

题库维护不易,您的支持就是我们最大的动力!