美文网首页
从 JSX 到 初识 createElement 源码

从 JSX 到 初识 createElement 源码

作者: 喜悦的狮子 | 来源:发表于2020-09-02 23:24 被阅读0次
React 源码:第 1 篇

学习源码的目的是为了更好得解决问题,越接近本质就越能解决复杂问题。同时读 React 源码是在汲取世界上最顶尖的前端工程师的养分,提升编码水平。

源码版本 16.7.0

1. JSX 与 关注点分离

JSX 简介,以上是 React 官方对 JSX 的介绍。

使用 React 最常用的就是使用 JSX 语法了。JSX 让 HTML 标签以及生成这些标签的代码内在得紧密联系在一起,于是我们不需要把单个组件拆分成视图和模板文件,反而是为每一个小的关注点创造一个独立的组件,并且把所有的逻辑和标签封装在其中。

这种设计原则叫做 “关注点分离”,目的是为了更有效得理解,设计和管理有许多功能互相依存的复杂系统,以便功能可以重用,独立于其他功能进行优化。

举个例子:配电将炉子保持在一个电路,而灯光则保持在另一个电路上,这样炉子的超载就不会影响灯光。

当然JSX 的优点还有很多,包括让代码更加直观,对代码的抽象能力……

2. JSX 是如何运行的?

Babel 转化网站

<div id="id">stone</div>

// 以下是 JSX 代码 用 Babel 转化出来的原生 JavaScript 代码。

"use strict";

/*#__PURE__*/
React.createElement("div", {
  id: "id"
}, "stone");

我们可以看到 JSX 语法的实质上是用 React 的 API —— createElement 创造节点。

它有三个参数,第 1 个参数是节点的类型,如果是原生的标签会是字符串;第 2 个参数是一个对象,包含了所有节点上的属性;第 3 个参数是节点内的内容。

再看一个复杂的例子。

function Comp() {
    return <a>123</a>
}

<Comp id="id">
    <span></span>
    <span></span>
    <div>stone</div>
</Comp>

// 以下是 JSX 代码用 Babel 转化出来的原生 JavaScript 代码。

"use strict";

function Comp() {
  return /*#__PURE__*/React.createElement("a", null, "123");
}

/*#__PURE__*/
React.createElement(Comp, {
  id: "id"
},
 /*#__PURE__*/React.createElement("span", null), 
/*#__PURE__*/React.createElement("span", null), 
/*#__PURE__*/React.createElement("div", null, "stone"));

如上图所示,组件在转化为原生 JavaScript 后,名称变成了一个变量,同时组件内部的多个标签转化为了,第 4,第 5,第 6个参数。

function stone() {
    return <a>Stone</a>
}

<stone id="id">
    <span></span>
    <span></span>
    <div>stone</div>
</stone>

// 以下是 JSX 代码用 Babel 转化出来的原生 JavaScript 代码。

"use strict";

function stone() {
  return /*#__PURE__*/React.createElement("a", null, "Stone");
}

/*#__PURE__*/
React.createElement("stone", {
  id: "id"
}, /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement("div", null, "stone"));

要注意的是,如果组件名称是小写会被转化成 字符串,从而在运行时发生错误。

3、createElement 实现

现在我们知道 JSX 的实现要依赖于 createElement,首先我们找到 React 的入口文件 React.js。

目录

然后在 React.js 中找到目标 createElement 函数的文件 ReactElement。


React.js

以下是源码,以及我的部分注释,可以看到 createElement如何处理这 3 个参数,设计理念与部分细节后续补充。

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */

// 1、type,原生节点会传入字符串,如果是组件就会传入一个变量,并且组件首字母需要大写。
// 2、节点属性都会存到config中
// 3、children就是标签的内容
export function createElement(type, config, children) {
  let propName; 

  // Reserved names are extracted
  const props = {};

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

  // 在属性中找到合理的合理的Ref 和合理的 key
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

  // hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
  // Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。
  
  // const hasOwnProperty = Object.prototype.hasOwnProperty;
  // 判断config对象中是否有 ref 属性
  // function hasValidRef(config) {
  //   if (__DEV__) {
  //     if (hasOwnProperty.call(config, 'ref')) {
  //       const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
  //       if (getter && getter.isReactWarning) {
  //         return false;
  //       }
  //     }
  //   }
  //   return config.ref !== undefined;
  // }

  // function hasValidKey(config) {
  //   if (__DEV__) {
  //     if (hasOwnProperty.call(config, 'key')) {
  //       const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
  //       if (getter && getter.isReactWarning) {
  //         return false;
  //       }
  //     }
  //   }
  //   return config.key !== undefined;
  // }


    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
 
    // const RESERVED_PROPS = {
    //   key: true,
    //   ref: true,
    //   __self: true,
    //   __source: true,
    // };
    // 以上是定义的内嵌属性
    // this.props中是不会有这些内嵌属性的,因为在这里就把他们处理掉了

    // 判断属性是否是内嵌的属性,如果不是就放到一个props对象里。如果不是就不会放
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  // children 是可以有多个的,后续的参数被认为是children
  // 用argument.length - 2的长度来代表 children的长度
  // 如果长度是1就把这个 chilren直接放进去
  // 如果长度 > 1,就会组成一个数组 包含所有的children
  // 然后吧这个数组放到props.children属性中
  const childrenLength = arguments.length - 2; 
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // 生成一个长度为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;
  }

  // Resolve default props 
  // 设置组件默认值
  // Comp.defaultProps = { value: 1};
  
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  // 判断原生节点还是组件
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }

  // function defineKeyPropWarningGetter(props, displayName) {
  //   const warnAboutAccessingKey = function() {
  //     if (!specialPropKeyWarningShown) {
  //       specialPropKeyWarningShown = true;
  //       warningWithoutStack(
  //         false,
  //         '%s: `key` is not a prop. Trying to access it will result ' +
  //           'in `undefined` being returned. If you need to access the same ' +
  //           'value within the child component, you should pass it as a different ' +
  //           'prop. (https://fb.me/react-special-props)',
  //         displayName,
  //       );
  //     }
  //   };
  //   warnAboutAccessingKey.isReactWarning = true;
  //   Object.defineProperty(props, 'key', {
  //     get: warnAboutAccessingKey,
  //     configurable: true,
  //   });
  // }
  
  // function defineRefPropWarningGetter(props, displayName) {
  //   const warnAboutAccessingRef = function() {
  //     if (!specialPropRefWarningShown) {
  //       specialPropRefWarningShown = true;
  //       warningWithoutStack(
  //         false,
  //         '%s: `ref` is not a prop. Trying to access it will result ' +
  //           'in `undefined` being returned. If you need to access the same ' +
  //           'value within the child component, you should pass it as a different ' +
  //           'prop. (https://fb.me/react-special-props)',
  //         displayName,
  //       );
  //     }
  //   };
  //   warnAboutAccessingRef.isReactWarning = true;
  //   Object.defineProperty(props, 'ref', {
  //     get: warnAboutAccessingRef,
  //     configurable: true,
  //   });
  // }
  
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

参考资料:
1、源码分析手册
2、React源码深度解析 高级前端工程师必备技能
3、百度百科
4、React 官方文档

相关文章

网友评论

      本文标题:从 JSX 到 初识 createElement 源码

      本文链接:https://www.haomeiwen.com/subject/kelmsktx.html