面试题面试题面试题之react(一)
2ChaosReact Jsx 转换成真实 DOM 的过程
一、是什么
react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后 React会将这些「变化」更新到屏幕上
在前面文章了解中,JSX通过babel最终转化成React.createElement这种形式,例如:
1 2 3 4
| <div> <img src="avatar.png" className="profile" /> <Hello /> </div>
|
会被bebel转化成如下:
1 2 3 4 5 6 7 8 9
| React.createElement( "div", null, React.createElement("img", { src: "avatar.png", className: "profile", }), React.createElement(Hello, null) );
|
在转化过程中,babel在编译时会判断 JSX 中组件的首字母:
最终都会通过RenderDOM.render(...)方法进行挂载,如下:
1
| ReactDOM.render(<App />, document.getElementById("root"));
|
二、过程
在react中,节点大致可以分成四个类别:
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class ClassComponent extends Component { static defaultProps = { color: "pink", }; render() { return ( <div className="border"> <h3>ClassComponent</h3> <p className={this.props.color}>{this.props.name}</p> </div> ); } }
function FunctionComponent(props) { return ( <div className="border"> FunctionComponent <p>{props.name}</p> </div> ); }
const jsx = ( <div className="border"> <p>xx</p> <a href=" ">xxx</a> <FunctionComponent name="函数组件" /> <ClassComponent name="类组件" color="red" /> </div> );
|
这些类别最终都会被转化成React.createElement这种形式
React.createElement其被调用时会传⼊标签类型type,标签属性props及若干子元素children,作用是生成一个虚拟Dom对象,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function createElement(type, config, ...children) { if (config) { delete config.__self; delete config.__source; } const props = { ...config, children: children.map((child) => typeof child === "object" ? child : createTextNode(child) ), }; return { type, props, }; } function createTextNode(text) { return { type: TEXT, props: { children: [], nodeValue: text, }, }; } export default { createElement, };
|
createElement会根据传入的节点信息进行一个判断:
- 如果是原生标签节点, type 是字符串,如 div、span
- 如果是文本节点, type 就没有,这里是 TEXT
- 如果是函数组件,type 是函数名
- 如果是类组件,type 是类名
虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:
1
| ReactDOM.render(element, container[, callback])
|
当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 diff算法进行高效的更新
如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行
render大致实现方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| function render(vnode, container) { console.log("vnode", vnode); const node = createNode(vnode, container); container.appendChild(node); }
function createNode(vnode, parentNode) { let node = null; const { type, props } = vnode; if (type === TEXT) { node = document.createTextNode(""); } else if (typeof type === "string") { node = document.createElement(type); } else if (typeof type === "function") { node = type.isReactComponent ? updateClassComponent(vnode, parentNode) : updateFunctionComponent(vnode, parentNode); } else { node = document.createDocumentFragment(); } reconcileChildren(props.children, node); updateNode(node, props); return node; }
function reconcileChildren(children, node) { for (let i = 0; i < children.length; i++) { let child = children[i]; if (Array.isArray(child)) { for (let j = 0; j < child.length; j++) { render(child[j], node); } } else { render(child, node); } } } function updateNode(node, nextVal) { Object.keys(nextVal) .filter((k) => k !== "children") .forEach((k) => { if (k.slice(0, 2) === "on") { let eventName = k.slice(2).toLocaleLowerCase(); node.addEventListener(eventName, nextVal[k]); } else { node[k] = nextVal[k]; } }); }
function updateFunctionComponent(vnode, parentNode) { const { type, props } = vnode; let vvnode = type(props); const node = createNode(vvnode, parentNode); return node; }
function updateClassComponent(vnode, parentNode) { const { type, props } = vnode; let cmp = new type(props); const vvnode = cmp.render(); const node = createNode(vvnode, parentNode); return node; } export default { render, };
|
三、总结
在react源码中,虚拟Dom转化成真实Dom整体流程如下图所示:

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