【React】(一) JSX渲染成真实Dom的基本过程

最近在深入学习React的源码以及原理,这里将学到的知识记录下来。暂时只学到了基本过程,详细的内部函数原理后面有机会再记录下来。

一、编译JSX

首先,JSX本质其实是javascript的语法扩展,和模板语言非常接近,但是其充分具备javascript的能力。但是其要在javascript生效的话,需使用到 Babel 进行编译,JSX在被编译后,会变成一个针对 React.createElement 的调用。

二、React.createElement 内部流程

首先,React.createElement 会接收三个参数:
第一个参数为 type,字符串类型,用于标识节点的类型,比如,div、p等,也可以是React组件类型或 React fragment类型。
第二个参数为 config,这是一个对象类型的参数,组件的所有属性都会键值对的形式存储在config中。
第三个参数为 children,这也是一个对象类型的参数,它记录的是组件标签之间嵌套的内容,也就是所谓的子节点或子元素。

React.createElement 源码:

/**

 101. React的创建元素方法

 */

export function createElement(type, config, children) {

  // propName 变量用于储存后面需要用到的元素属性

  let propName; 

  // props 变量用于储存元素属性的键值对集合

  const props = {}; 

  // key、ref、self、source 均为 React 元素的属性,此处不必深究

  let key = null;

  let ref = null; 

  let self = null; 

  let source = null; 



  // config 对象中存储的是元素的属性

  if (config != null) { 

    // 进来之后做的第一件事,是依次对 ref、key、self 和 source 属性赋值

    if (hasValidRef(config)) {

      ref = config.ref;

    }

    // 此处将 key 值字符串化

    if (hasValidKey(config)) {

      key = '' + config.key; 

    }

    self = config.__self === undefined ? null : config.__self;

    source = config.__source === undefined ? null : config.__source;

    // 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面

    for (propName in config) {

      if (

        // 筛选出可以提进 props 对象里的属性

        hasOwnProperty.call(config, propName) &&

        !RESERVED_PROPS.hasOwnProperty(propName) 

      ) {

        props[propName] = config[propName]; 

      }

    }

  }

  // childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度

  const childrenLength = arguments.length - 2; 

  // 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了

  if (childrenLength === 1) { 

    // 直接把这个参数的值赋给props.children

    props.children = children; 

    // 处理嵌套多个子元素的情况

  } else if (childrenLength > 1) { 

    // 声明一个子元素数组

    const childArray = Array(childrenLength); 

    // 把子元素推进数组里

    for (let i = 0; i < childrenLength; i++) { 

      childArray[i] = arguments[i + 2];

    }

    // 最后把这个数组赋值给props.children

    props.children = childArray; 

  } 



  // 处理 defaultProps

  if (type && type.defaultProps) {

    const defaultProps = type.defaultProps;

    for (propName in defaultProps) { 

      if (props[propName] === undefined) {

        props[propName] = defaultProps[propName];

      }

    }

  }



  // 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数

  return ReactElement(

    type,

    key,

    ref,

    self,

    source,

    ReactCurrentOwner.current,

    props,

  );

}

整体来说,createElement的大致流程为:
1.二次处理key、ref、self、source四个属性值;
2.遍历config,筛选可以提到props中的属性;
3.将children中的子元素推入childArray数组;
4.格式化defaultProps
5.将以上数据作为入参,发起ReactElement的调用,最终由ReactElement返回虚拟Dom对象

三、最终将虚拟Dom传入ReactDom.render函数中,将其转变为真实Dom

上面我们讲到React.createElement会将最后处理的值传给ReactElement,其实ReactElement拿到值过后,只是做了非常简单的操作——组装。

ReactElement 源码:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // 内置属性赋值
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 记录创造该元素的组件
    _owner: owner,
  };

  // 
  if (__DEV__) {
    // 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
  }

  return element;
};

将所有从React.createElement中收到的值组装成一个React的虚拟Dom,最终调用ReactDom.render方法去实现转化。
ReactDom.render 接收三个参数,其中第一个参数便是生成的虚拟Dom,第二个参数则是一个真实Dom,此Dom相当于是一个容器
,React元素将被渲染到这个容器里面去,第三个参数则是一个callback function。
由此,整个从JSX到真实Dom的基本过程就完成。大致流程为:
书写JSX代码 => Babel编译JSX => 编译后的JSX执行React.createElement的调用 => 传入到ReactElement方法中生成虚拟Dom => 最终返回给ReactDom.render生成真实Dom

你可能感兴趣的:(React,reactjs)