美文网首页
从 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