React系列——JSX介绍

一、什么是JSX

XML + JavaScript结合的一种形式,是JavaScript的语法扩展,充分具备了JavaScript的能力。在React中本质是React.createElement()的语法糖

二、为什么要使用JSX

JSX允许前端开发者使用类HTML标签语法来创建虚拟DOM,在降低学习成本的同时,也提升了研发效率和研发体验。

三、JSX在React中是如何生效的

Babel会把JSX转译为一个名为React.createElement() 函数调用。React.createElement() 会返回一个“React Element”的JS对象,这个对象也被称为“React 元素”。示例如下:

//代码取自React官网
const element = (
  <h1 className="greeting">
  Hello, world!
  </h1>
);
//通过babel会转化为如下:
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

四、JSX是如何映射为DOM的:探究React.createElement源码

createElement源码如下,代码解析已经加入到对于的代码块前。

/**
 * @param {string} type 用于标识节点的类型
 * @param {object} config 组件的属性,键值对形式
 * @param {object} children 组件标签间嵌套的内容
 * @returns React.Element()函数调用
 */
function createElement(type, config, children){
  let propName;
  const props = {};


  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  //处理config 
  //config对象中存放的是元素的属性
  if (config != null) {
    // 给ref属性赋值 hasValidRef验证是否含有ref属性
    if (hasValidRef(config)) {
      ref = config.ref;
      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    // 将key字符串化  hasValidKey验证是否含有key属性
    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      key = '' + config.key;
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    //遍历config,将除了上面这四个属性以外的属性添加到props中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // childrenLength是当前元素子元素的个数,arguments的长度减去2是减去的type和config占用的长度
  const childrenLength = arguments.length - 2;
  // 如果除去type和config,只剩下一个参数,通常情况下是文本节点,则直接赋值给props.children
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // 如果有多个子元素,则将子元素推入childArray 并赋值给props.children
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // 处理defaultProps
  // 如果type中存在defaultProps,遍历defaultProps,将这些属性在props中值为undefined的重新赋值
   if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  if (__DEV__) {
    // 判断元素是否存在key和ref属性
    if (key || ref) {
      // 校验type是不是一个函数,如果是函数,就表示当前元素是组件。如果不是函数直接返回元素类型字符串
      // displayName 用于报错过程中显示哪个组件报错了,如果开发者定义了displayName就显示开发者定义的,否则显示组件名称,如果组件没有名称显示'Unknown'
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      // 如果key属性存在,给props对象添加key属性 并指定使用props.key时报错
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      // 如果ref属性存在,给props对象添加ref属性 并指定使用props.ref时报错
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 返回ReactElement的调用
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

}

阅读完上面的代码后,可以发现createElement函数是一个数据处理函数,处理好参数后发起React.Element函数的调用。那么React.Element又是何方神圣?请继续阅读接下来关于React.Element的源码解析。

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
  	// 定义元素类型
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,


  	// 记录创建元素的组件
    _owner: owner,
  };
	// 针对DEV环境下的一些处理
  if (__DEV__) {
    element._store = {};


    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });

    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });

    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

阅读完ReactElement的源码后发现,这个函数的代码非常简单,仅仅是将参数组装成一个ReactElement对象返回给了createElement。这个ReactElement对象其实就是我们常说的虚拟DOM。
那么这个虚拟DOM又是如何映射成为真实DOM的呢?在React18版本之前都是通过ReactDOM.render()方法实现的,在React 18中,render 函数已被 createRoot 函数所取代。(渲染过程会在其他篇章做详细解析,此处不再赘述)

五、总结

开发者编写JSX代码通过babel编译为React.createElement()函数的调用,此函数通过调用ReactElement函数返回一个ReactElement对象(又称“虚拟DOM”),再通过ReactDOM.render / ReactDOM.createRoot函数将虚拟DOM渲染成为真实DOM。最后一图带你看懂JSX是如何变成真实DOM的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxJYyxm' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片