• React之Jsx如何转换成真实DOM


    一、是什么

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

    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=" ">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. // ! 源码中做了详细处理,⽐如过滤掉keyref
    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,使用方法如下:

    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. }
    39. function updateNode(node, nextVal) {
    40. Object.keys(nextVal)
    41. .filter(k => k !== "children")
    42. .forEach(k => {
    43. if (k.slice(0, 2) === "on") {
    44. let eventName = k.slice(2).toLocaleLowerCase();
    45. node.addEventListener(eventName, nextVal[k]);
    46. } else {
    47. node[k] = nextVal[k];
    48. }
    49. });
    50. }
    51. // 返回真实dom节点
    52. // 执行函数
    53. function updateFunctionComponent(vnode, parentNode) {
    54. const {type, props} = vnode;
    55. let vvnode = type(props);
    56. const node = createNode(vvnode, parentNode);
    57. return node;
    58. }
    59. // 返回真实dom节点
    60. // 先实例化,再执行render函数
    61. function updateClassComponent(vnode, parentNode) {
    62. const {type, props} = vnode;
    63. let cmp = new type(props);
    64. const vvnode = cmp.render();
    65. const node = createNode(vvnode, parentNode);
    66. return node;
    67. }
    68. export default {
    69. render
    70. };

    三、总结

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

    其渲染流程如下所示:

    • 使用React.createElement或JSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...) ,Babel帮助我们完成了这个转换的过程。
    • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
    • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM
  • 相关阅读:
    获取当前时间为本周的第几小时,getHourOfWeek
    Ubuntu部署运行ORB-SLAM2
    day09渗透简单测试流程以及PKI实验
    外卖App点菜页-两个tableView联动【1】
    django建站过程(3)定义模型与管理页
    你好,面试官 | 终于上岸了,你会哪些 JVM 调优参数?呆住了。。。
    【python】如何注释
    如何从0到1搭建一个个人网站
    四、看看 CSS 创建
    我在高职教STM32——GPIO入门之按键输入(2)
  • 原文地址:https://blog.csdn.net/Ming_xm/article/details/134028491