乐趣区

关于前端:react的jsx和ReactcreateElement是什么关系面试常问

1、JSX

在 React17 之前,咱们写 React 代码的时候都会去引入 React,并且本人的代码中没有用到,这是为什么呢?

这是因为咱们的 JSX 代码会被 Babel 编译为 React.createElement,咱们来看一下 babel 的示意模式。

须要留神的是:

  • 自定义组件时须要首字母用大写,会被辨认出是一个组件,这是一个规定。
  • 小写默认会认为是一个 html 标签,编译成字符串。

论断:JSX 的实质是 React.createElement 这个 JavaScript 调用的语法糖。是 JS 的语法扩大

2、React.createElement 源码浏览

从下面咱们晓得 jsx 通过 babel 编译成 React.createElement,上面咱们就去看一下相干源码:

2.1 入参解读

入参解读:发明一个元素须要晓得哪些信息

export function createElement(type, config, children)

createElement 有 3 个入参,这 3 个入参囊括了 React 创立一个元素所须要晓得的全副信息。

  • type:用于标识节点的类型。它能够是相似“h1”“div”这样的规范 HTML 标签字符串,也能够是 React 组件类型或 React fragment 类型。
  • config:以对象模式传入,组件所有的属性都会以键值对的模式存储在 config 对象中。
  • children:以对象模式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”。
React.createElement("ul", {
  // 传入属性键值对
  className: "list"
   // 从第三个入参开始往后,传入的参数都是 children
}, React.createElement("li", {key: "1"}, "1"), React.createElement("li", {key: "2"}, "2"));

对应的 DOM 构造

<ul className="list">
  <li key="1">1</li>
  <li key="2">2</li>
</ul>

从入口文件 React.js 文件可知,React.createElement 办法是从 ReactElement 文件引入进来的,咱们就进入这个文件,定位到 createElement 办法。

2.1.1 先来看 config 参数的解决

// 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]; 
      }
    }
  }

这段代码对 ref 以及 key 做了个验证解决,具体如何验证咱们先不关怀,从办法名称上来分别一下,而后遍历 config 并把属性提进 props 对象里。

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

也就是把 ref 和 key 剔除。

2.1.2 接下来是一段对于 children 的操作

// 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; 
  } 

首先把第二个参数之后的参数取出来,而后判断长度是否大于一。大于一的话就代表有多个 children,这时候 props.children 会是一个数组,否则的话只是一个对象。

2.1.3 最初返回一个调用 ReactElement 执行办法,并传入方才解决过的参数

参考 前端进阶面试题具体解答

// 最初返回一个调用 ReactElement 执行办法,并传入方才解决过的参数
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

2.1.4 解决传入的 defaultProps

// 解决 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];
      }
    }
  }

2.2 小结

createElement 中并没有十分复杂的波及算法或实在 DOM 的逻辑,它的每一个步骤简直都是在格式化数据。

3、出参解读

下面曾经剖析过,createElement 执行到最初会 return 一个针对 ReactElement 的调用。

3.1 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;
};

$$typeof 来帮忙咱们辨认这是一个 ReactElement

3.2 小结

ReactElement 其实只做了一件事件就是组装数据。

能够在 React 中尝试打印:

const AppJSX = (<div className="App">
  <h1 className="title">I am the title</h1>
  <p className="content">I am the content</p>
</div>)

console.log(AppJSX)

失去的控制台后果:

这个 ReactElement 对象实例,实质上是以 JavaScript 对象模式存在的对 DOM 的形容,也就是虚构 DOM

3.3 扩大常识

既然是虚构 DOM,就意味着和渲染到页面上的实在 DOM 不是一个货色,那就须要用 ReactDOM.render 办法来渲染实在 DOM。

ReactDOM.render(
    // 须要渲染的元素(ReactElement)element, 
    // 元素挂载的指标容器(一个实在 DOM)container,
    // 回调函数,可选参数,能够用来解决渲染完结后的逻辑
    [callback]
)

ReactDOM.render 办法能够接管 3 个参数,其中第二个参数就是一个实在的 DOM 节点,这个实在的 DOM 节点充当“容器”的角色,React 元素最终会被渲染到这个“容器”外面去。比方,示例中的 App 组件,它对应的 render 调用是这样的:

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
退出移动版