[React源码系列2]React元素渲染原理

306 阅读3分钟

一、元素渲染

1.1 React元素

元素是组成React应用的最小单位,与浏览器的DOM元素不同,React元素是通过解析JSX生成的普通对象(virtual dom),被用来模拟真实DOM。而React可以以极低的开销通过元素来创建真实的DOM,其中ReactDOM就是用来负责渲染和更新DOM的。

1.2 元素的渲染原理

元素的渲染原理就是将 JSX 转换成 vdom ,再经过一系列的操作生成 真实DOM,再将真实DOM插入容器中。

元素渲染原理.png

值得注意的是React元素是不可变对象,也就代表着一旦创建就无法更改元素。像动画的单独一帧,只能代表一个特定的时间点。(React17之前这只是官方约定此对象不可变,在React17之后,源码中在开发环境中直接将整个React元素给Object.freeze()了)

二、源码实现

2.1 createElement

createElement是用来将 JSX 转换成 vdom 的方法

  • children的处理
  1. 传入多个儿子,将props.children处理成数组
  2. 传入一个儿子,将props.children处理成对象
  3. 没有传入儿子,将props.children就是undefined
function createElement(type,config,children){
	let props = {...config};
	if(argument.length > 3){
		children = Array.prototype.slice.call(arguments,2).map(warpToVdom)
	}else{
        if(typeof children !== 'undefined'){
            props.children = warpToVdom(children);
        }
    }
	return {
		type,
		props
	}
}

// React的源码对于文本和数字有做特殊,这里方便理解简单,统一将它包装成vdom(源码没有这一步,只是方便写伪代码)
export const REACT_TEXT  = Symbol('REACT_TEXT');
function warpToVdom(element){
    if(typeof element === 'string' || typeof element === 'number'){
        return {
            type: REACT_TEXT,
            props:{
                content:element
            }
        }
    }else{
        return element;
    }
}

2.2 ReactDOM

vdom渲染的方法统一放在ReactDOM

2.2.1 render

render函数很好理解,就是将 vdom 创建成真实DOM,再将真实DOM插入容器中

function render(vdom,container){
    let newDOM = createDOM(vdom);
    container.appendChild(newDOM);
}

2.2.2 createDOM 创建真实DOM

  1. 根据 vdom 的 type 创建真实元素
  2. 再根据 props 更新真实元素的属性
  3. 最后通过props.children来判断是否递归执行render
function createDOM(vdom){
    let {type,props} = vdom;
    let dom;
    // 1. 创建DOM
    if(type === REACT_TEXT){
        dom = document.createTextNode(props.content);
    }else{
        dom = document.createElement(props.content);
    }
    
    if(props){
        // 2. 更新属性
        updateProps(dom,{},props);
        
        // 3. 递归渲染
        if(typeof props.children == 'object' && props.children.type){
           render(props.children,dom)
        }else if(Array.isArray(props.children)){
            reconcileChildren(props.children,dom)
        }
    }
    vdom.dom = dom
    return dom
}

2.2.3 updateProps 更新属性

function updateProps(dom,oldProps,newProps){
    for(let key in newProps){
        if(key === 'children'){
            continue;
        }
        if(key === 'style'){
            let styleObj = newProps[style];
            for(let attr in styleObj){
                dom.style[attr] = styleObj[sttr]
            }
        }else{
            dom[key] = newProps[key]
        }
    }
}

2.2.4 reconcileChildren

props.children是对象时直接render,是数组时通过reconcileChildren遍历出 child 再render

function reconcileChildren(childrenVdom,parentDOM){
    for(let i=0;i<childrenVdom.length;i++){
        let childVdom = childrenVdom[i];
        render(childVdom,parentDOM)
    }
}

2.2.5 导出

最后将render方法导出即可

const ReactDOM = {
    render
}
export default ReactDOM;

三、结语

这套思路是借鉴了React15的源码来实现的,和现在的最新版的代码有一些出入,是由于16中新增的Fiber会使virtual DOM进行增量式渲染,直接手写的话会劝退大部分萌新玩家,之所以选择React15是由于这个渲染的过程更易于理解,当然后续也是会更新Fiber相关的内容的。对Fiber感兴趣的话可以看看这个--react-fiber-architecture。最后点个赞再走吧~~