说一下 React JSX 原理吧!

323 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情

JSX

  • JSX 是 JavaScript 的语法扩展。它类似于模板语言,但它具有 JavaScript 的全部功能。
  • JSX会被编译为React.createElement() ,返回React Element 的JavaScript 对象。基本的介绍JSX看到这里的文档,深入的教程在这里

React.createElement()是什么?

其实JSX,是让开发者写起来比较高效,像HTML似的,但是React只认React.createElement元素,那就通过Babel工具进行代码的转换。所以对于开发者而言 JSX + Babel === React.createElement

从上面还可以看出为什么 JSX 具有 JavaScript 的全部功能。

babel-preset-react-app这个语法解析包,把jsx语法转换成一个名为 React.createElement()的代码字符串。

源码解析

createElement

主要作用是对jsx参数进行格式化,传递到ReactElement方法中。

比如 jsx
<ul className="list">
  <li key="1">1</li>
  <li key="2">2</li>
</ul>
​
babel-preset-react-app 将上面的JSX转换成下面的React能识别的代码
React.createElement("ul", {
    // 传入属性键值对
    className: "list"
     // 从第三个入参开始往后,传入的参数都是 children
  }, React.createElement("li", {
    key: "1"
  }, "1"), React.createElement("li", {
    key: "2"
  }, "2"));
​
​
​
/**
  React的创建元素方法
 */
type:用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串,也可以是 React 组件类型「function:表示function component、class:表示class compnent」或 React fragment 类型。
config:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。
children:以对象形式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”。
​
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执行方法,并传入刚才处理过的参数的 ReactElement的JavaScript对象。
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

ReactElement

ReactElement生成虚拟dom, ReactElement 只对 createElement传递过来的参数进行组装,稍作修改,然后返回。

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.render

将虚拟dom进行渲染。

ReactDOM.render(
    // 需要渲染的元素(ReactElement也叫虚拟dom)
    element, 
    // 元素挂载的目标容器(一个真实DOM)
    container,
    // 回调函数,可选参数,可以用来处理渲染结束后的逻辑
    [callback]
)

JSX 语法

需要补充知识,

reactjs.org/docs/introd…

reactjs.org/docs/jsx-in…

  1. 定义虚拟dom时,不要写引号。

  2. 标签中混入js表达式要用{}。

  3. 样式的类名指定不要用class,要用className。

  4. 内联样式,要用style={{key:value}}。

  5. 只有一个根标签。

  6. 标签必须闭合。

  7. 标签首字母

    • 若小写字母开头,则将该标签转为html中同名元素,若html中没有对应标签的同名元素,则报错。
    • 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
  8. js语句(代码)与js表达式区别。

    js语句不能写在jsx里面。

    • 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
    • 语句:if for switch 。

各位看官如遇上不理解的地方,或者我文章有不足、错误的地方,欢迎在评论区指出,感谢阅读。