React源码解读01,页面初次渲染

1. 页面初次渲染

JSX语法的转换

  1. 先看看我们使用React打印HelloWorld

    ReactDOM.render((
      <h1 style="color:red">Hello World</h1>
    ),document.getElementById('root'));
    
  2. 使用React.createElement包裹

    ReactDOM.render((
        React.createElement("h1",{style:"color:red"},"Hello World")
    ), document.getElementById('root'));
    
  3. 根据这两个结果,我们查看打包后的代码。我们不难得知,使用JSX语法,在打包时转换成React.createElement函数。
    在这里插入图片描述

1.1 虚拟dom

  1. 虚拟dom就是一个对象,用来描述一个元素的具体属性。在React中,我们通过React.createElement得到虚拟dom。接着将虚拟dom通过React.render()转化成真实dom渲染到界面中。
  2. React.createElement需要传入三个参数,type(元素类型),props(元素的属性),children(元素的孩子)
  3. 下面是React.createElement()简易版的代码
    import Component from './Component';
    function createElement(type,config,children){
        if(config){
            delete config.__source;
            delete config.__self;
        }
       let props = {...config};
       if(arguments.length>3){ // 当孩子超过一个时,我们将孩子们放到一个数组中
        children=Array.prototype.slice.call(arguments,2);
       }
       // 将孩子数组放到属性中。
       props.children=children;
       return {
           type,
           props
       }
    }
    const React = {createElement,Component}
    export default React;
    

最终的到的虚拟dom有两个属性,type(类型),props(属性,包含孩子的数组),不仅如此,如果当传入的孩子不是数字,字符串时,通过会被React.createElement包裹。如:

ReactDOM.render((
    <div>
        <h1>
            你好呀
        </h1>
        <h1>
            你好呀
        </h1>
    </div>
), document.getElementById('root'));
// 在打包时,转换成
ReactDOM.render((
    React.createElement("div", {}, React.createElement("h1", {}, "你好啊"), React.createElement("h1", {}, "你好啊"))
), document.getElementById('root'));

2 将虚拟dom转换为真实dom

  1. 上面说到,得到虚拟dom之后,调用React.render()函数将虚拟dom渲染到页面中。
  2. 那我们很容易就能想到,第一步要将虚拟dom渲染成真实dom,接着将真实的dom放到我们指定的dom元素下(document.getElementById(‘root’))
    以下代码模拟以上过程。接着我们需要编写createDOM()来得到真实dom
    function render(vdom,container){
        const dom = createDOM(vdom);
        container.appendChild(dom);
    }
    
2.1 编写 createDOM
  1. 直接上代码
    function createDOM(vdom){
        //如果vdom是数字或者字符串的话,直接返回一个真实的文本节点
        if(typeof vdom === 'string'||typeof vdom === 'number'){
            return document.createTextNode(vdom);
        }
        //否则 它就是一个虚拟DOM对象了,也就是React元素
        let {type,props}= vdom;
        let dom;
        if(typeof type === 'function'){//自定义的函数组件
            if(type.isReactComponent){//说明这是一个类组件
                return mountClassComponent(vdom);
            }else{//否则说明是一个函数组件
                return mountFunctionComponent(vdom);
            }
        }else{//原生组件
            dom = document.createElement(type);
        }
        //使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
        updateProps(dom,props);
        //在这处理props.children属性
        //如果只有一个儿子,并且这个儿子是一个文本
        if(typeof props.children ==='string'||typeof props.children ==='number'){
            dom.textContent = props.children;
         //如果只有一个儿子,并且这个儿子是一个虚拟DOM元素   
        }else if(typeof props.children ==='object' && props.children.type){
            //把儿子变成真实DOM插到自己身上,形成递归!
            render(props.children,dom);
        //如果儿子是一个数的话,说明儿子不止一个
        }else if(Array.isArray(props.children)){
            reconcileChildren(props.children,dom);
        }else{
            document.textContent = props.children?props.children.toString():"";
        }
        return dom;
    }
    

这段代码的逻辑很简单,拿到虚拟dom,判断是否为字符串跟数字。不是的话就判断是否为自定义的函数组件(分别为函数组件跟类组件)或者是普通的标签,得到一个真实的dom,接着给该dom通过**updateProps(dom,props);**将虚拟dom中的属性放到真实dom中。接着处理它的孩子们即可。

2.2 编写 updateProps(dom,props)
  1. 迭代传入的 props ,所有的属性绑定到dom中,注意:要跳过children,因为我们不是在这里处理它的孩子。

    function updateProps(dom,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[attr];
            }
          }else if(key.startsWith('on')){
            //给真实DOM加属性的话 onclick
            //dom[key.toLocaleLowerCase()]=newProps[key];
            addEvent(dom,key.toLocaleLowerCase(),newProps[key]);
          }else{//在JS中 dom.className='title'
              dom[key]= newProps[key];
          }
      }
    }
    
处理自定义函数式组件
  1. 类组件

    1. 该组件继承React.Component,所以我们需要编写Component作为父类,根据我们平时使用习惯可以推断出它最起码有 props与state属性,并且实例调用render方法能返回一个jsx编写的界面
    class Component{
    	// 该静态属性用于标识该组件为类组件
        static isReactComponent = true
        constructor(props){
        	// 在被 new 时,props传入
            this.props = props;
            this.state = {};
        }
    }
    
    1. 在createDOM()编写处理类组件的函数 mountClassComponent(vdom)
    function mountClassComponent(vdom){
        //解构类的定义和类的属性对象
        let {type,props}= vdom;
        //创建类的实例
        let classInstance = new type(props);
        //调用实例的render方法返回要渲染的虚拟DOM对象
        let renderVdom = classInstance.render(); // 我们在子类编写的render(),返回虚拟dom
        //根据虚拟DOM对象创建真实DOM对象
        let dom =  createDOM(renderVdom);
        //为以后类组件的更新,把真实DOM挂载到了类的实例上
        classInstance.dom = dom;
        return dom;
    }
    
  2. 函数组件
    最常见的组件编写方法,我们传入props,执行该函数,直接得到一个虚拟dom,然后调用createDOM()得到真实dom返回。

    function mountFunctionComponent(vdom){
        let {type,props}= vdom;
        let renderVdom = type(props);
        return createDOM(renderVdom);
    }
    
处理孩子们
  1. 如果孩子是字符串跟数字,直接放到dom中
  2. 如果孩子是对象,那么就是被React.createElement返回的虚拟dom对象,那就可以通过 render函数,传入当前孩子和当前dom递归,将孩子的真实dom挂载到当前dom上
  3. 如果孩子是数组,那就遍历该数组,依次调用render方法
  4. 如果啥都不是,那就转换成字符串挂载到dom上。

挂载到页面中

回到最初的render函数

function render(vdom,container){
    const dom = createDOM(vdom);
    container.appendChild(dom);
}

我们好不容易得到真实dom,通过appendChild(JS的原生方法),将dom作为根dom的孩子插入到root上。

综上,初次渲染完成!
感谢张仁阳老师的教导

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值