【 React 】React JSX 转换成真实DOM的过程?

1. 是什么

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

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

javascript 复制代码
<div>
     < img src="avatar.png" className="profile" />
    <Hello />
</div>

会被babel 转换成如下:

javascript 复制代码
React.createElement(
    "div",
    null,
    React.createElement("img", {
        src: "avatar.png",
        className: "profile"
    }),
    React.createElement(Hello, null) 
);

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

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

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

javascript 复制代码
ReactDOM.render(<App />, document.getElementById("root"));

2. 过程

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

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

如下所示:

javascript 复制代码
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对象,如下所示:

javascript 复制代码
function createElement(type, config, ...children) {
    if (config) {
    delete config.__self;
    delete config.__source; }
    //源码中做了详细处理,比如过滤掉 ! key ref等
    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,使用方法如下:

javascript 复制代码
ReactDOM.render(element,container[,callback])

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

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

现方法如下:

javascript 复制代码
function render(vnode, container) {
    console.log("vnode", vnode); //虚拟 DOM 对象
    // vnode _> node
    const node = createNode(vnode, container);
    container.appendChild(node); 
}
    // 创建真实DOM节点
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;
 }
    //遍历下子 vnode 然后把子vnode-> 真实DOM 再插入到父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") { 
            node.addEventListener(eventName, nextVal[k]); 
        } else {
                node[k] = nextVal[k]; 
        } 
    }); 
}
//返回真实 dom节点
// 执行函数
function updateFunctionComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let vvnode = type(props);
    const node = createNode(vvnode, parentNode);
    return node; 
}
            // dom
            // 先实例化,再执行render函数
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
};

3. 总结

渲染流程如下所示:

  • 使用React.createElement或JSX编写React组件,实际上所有的JSX代码最后都会转换成React.createElement(...),Babel帮助我们完成了这个转换的过程。
  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM
相关推荐
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排6 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60617 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角8 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!8 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰8 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi10 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen10 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马11 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu11 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss