美文网首页
react.createElement方法探究

react.createElement方法探究

作者: anddju | 来源:发表于2020-05-14 19:09 被阅读0次

    前言:今天偶有闲暇,突然想知道 虚拟DOM 是如何实现的,说到 虚拟DOM ,便不得不说说 createElement 方法。所以先看 createElement 方法,并于此记录一波。(下次在写这种源码指不定啥时候)

    目前看的版本: 16.13.1

    看源码准备工作

    第一步:clone项目

    第二步,搜索:createElement, 搜索结果中,唯一能和定义沾边的就是这个 React.d.ts 文件了

    搜索结果.png

    第三步:根据 React.d.ts,找到当前目录的 src,接着找 React.jsts语法我还没学过,不过看到 .ts,我就去找根目录下的同名文件,一般都能找到)。 看到这熟悉的声明语句,我就知道我找到了 。

    const createElement = __DEV__ ? createElementWithValidation : createElementProd;
    

    开始正式查看

    createElement 方法在 开发模式中使用 createElementWithValidation,在生产模式中使用 createElementProd。(开发者模式和生产模式的区分变量 __DEV__,为啥叫这个,一会我研究研究在发一篇文章,头一次考虑这么细)

    一般在开发者模式中,注释和代码缩进都会很不错,所以看开发者模式的方法 createElementWithValidation

    createElementWithValidation, 顾名思义 创建_元素_经过_校验。具体怎么校验和创建,咱们接着往下看。 (这个方法是相对引用的文件,怎么找我就不说了)

    createElementWithValidation 方法第一句。玩呢。。上来就是一函数。。 先看名字 isValidElementType,盲猜:是_有效_元素_类型, 点击去瞅瞅。

    isValidElementType.png

    得嘞,看完第一行的这个函数,咱们接着往下走。
    为了方便,我直接在代码注释内写理解了

      // We warn in this case but don't throw. We expect the element creation to
      // succeed and there will likely be errors in render.
      /***
        在这种情况下我们会警告但不要抛出错误。我们希望元素创建成功,并且呈现中可能会有错误。
      **/
      if (!validType) {  /*** 如果没有经过前面的类型校验 **/
        let info = '';
     /*** 如果是个 undefined 元素,或者 是个空对象, 我们就提示他:
    
    您可能忘记从定义组件的文件中导出组件,或者您可能混淆了默认导入和命名导入。(不得不说这个提示是真的友好)
     **/
        if (
          type === undefined ||
          (typeof type === 'object' &&
            type !== null &&
            Object.keys(type).length === 0)
        ) {
          info +=
            ' You likely forgot to export your component from the file ' +
            "it's defined in, or you might have mixed up default and named imports.";
        }
    
        /*** 获取出错文件路径 或者出错组件名称,方便查找, 查找路径的这个方法后边再看,跟我目标虚拟DOM 不是很沾边 **/
        const sourceInfo = getSourceInfoErrorAddendumForProps(props);
        if (sourceInfo) {
          /*** 如果存在引用信息,就提示出错路径和出错行数 **/
          info += sourceInfo;
        } else {
          /*** 没有引用,那就提示出错组件名称 **/
          info += getDeclarationErrorAddendum();
        }
        
    
      /*** 判断组件 类型,分为 null ,array, 自定义组件, 原生组件 **/
        let typeString;
        if (type === null) {
          typeString = 'null';
        } else if (Array.isArray(type)) {
          typeString = 'array';
        } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
          typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
          info =
            ' Did you accidentally export a JSX literal instead of a component?';
        } else {
          typeString = typeof type;
        }
    
        /*** 如果是开发模式,就拼接错误,并输出 **/
        if (__DEV__) {
          console.error(
            'React.createElement: type is invalid -- expected a string (for ' +
              'built-in components) or a class/function (for composite ' +
              'components) but got: %s.%s',
            typeString,
            info,
          );
        }
      }
    

    OK,第一大段落,错误验证就算结束了。接下来请看第二段,生成 element 元素

      /*** 创建元素,一个大函数,容后再解释 **/
      const element = createElement.apply(this, arguments);
    
      // The result can be nullish if a mock or a custom function is used.
     /*** 如果使用mock或自定义函数,则结果可能为空。 **/
      // TODO: Drop this when these are no longer allowed as the type argument.
    /*** 当不再允许这些参数作为类型参数时,删除此项。 **/
      if (element == null) {
    /*** 如果是 mock 或 自定义函数时, 返回 null元素 **/
        return element;
      }
    
      // Skip key warning if the type isn't valid since our key validation logic
      // doesn't expect a non-string/function type and can throw confusing errors.
      // We don't want exception behavior to differ between dev and prod.
      // (Rendering will throw with a helpful message and as soon as the type is
      // fixed, the key warnings will appear.)
    /***翻译
    如果类型无效,则跳过密钥警告,因为我们的密钥验证逻辑不需要非字符串/函数类型,并且可能会引发混淆错误。
    我们不希望生产模式和开发模式的异常处理有所不同。
    (提示一条有帮助的消息,在类型被修复之前,将显示关键警告。)
     **/
      if (validType) {
        for (let i = 2; i < arguments.length; i++) {
          /*** 函数大意:
    确保每个元素要么在静态位置传递,要么在定义了显式键属性的数组中传递,要么在具有有效键属性的对象文本中传递。 **/
          validateChildKeys(arguments[i], type);
        }
      }
    
    /*** 校验Props 有效性, 分为 fragment标签和 普通标签 **/
      if (type === REACT_FRAGMENT_TYPE) {
        validateFragmentProps(element);
      } else {
        validatePropTypes(element);
      }
    
    /*** 元素创建完毕,返回元素 **/
      return element;
    

    接下来,看重点。 createElement 方法 所在文件packages/react/src/ReactElement.js

    首先,函数部分上来就是这些声明,这些声明也标志了一个 react组件 需要的参数

      /***  props 名称 **/
    let propName;
    
    /*** 提取props ,放到 props变量内 **/
      // Reserved names are extracted
      const props = {};
    
      let key = null;  /*** 元素 key 值 **/
      let ref = null; /*** 元素ref **/
      let self = null;  /*** 元素上一级 **/
      let source = null; /*** 元素路径 **/
    

    校验 ref

    /*** 存在 ref ,并且有效  ,函数内 `Object.getOwnPropertyDescriptor(config, 'ref').get;`写的贼棒 **/
    if (hasValidRef(config)) {
          ref = config.ref;
    
          if (__DEV__) {
            /*** 开发模式中,如果ref 无效,提示的内容,应该是新版才会用的 useRef 和 createRef **/
            warnIfStringRefCannotBeAutoConverted(config);
          }
        }
    

    校验 key

     if (hasValidKey(config)) {
          key = '' + config.key;
        }
    

    selfsource 进行校验,赋值

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    

    props 属性处理,利用遍历和赋值

    // Remaining properties are added to a new props object
        for (propName in config) {
        /*** hasOwnProperty = Object.prototype.hasOwnProperty;  可遍历属性
       RESERVED_PROPS = {
      key: true,
      ref: true,
      __self: true,
      __source: true,
    };   没有这些属性
    **/
          if (
            hasOwnProperty.call(config, propName) &&
            !RESERVED_PROPS.hasOwnProperty(propName)
          ) {
            props[propName] = config[propName];
          }
        }
    

    迁移 children

    /*** 翻译
    子项可以是多个参数,并且这些子项被转移到新分配的props对象上。
     **/
    // Children can be more than one argument, and those are transferred onto
      // the newly allocated props object.
      const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        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 = childArray;
      }
    

    生成并返回 react元素

    return ReactElement(element.type, key, ref, self, source, owner, props);
    

    接下来是讲解 ReactElement 方法。 packages/react/src/ReactElement.js

    找到 ReactElement 方法之后,首先看到的就是这一大串注释
    我感觉挺有用,先翻译一波。

    /***
     * 方法介绍翻译:
         一个用于创造 react元素的 格式化(???这么说应该没啥毛病)方法。
         这里不再遵循class声明,请不要使用 new 来调用它。
         另外,检查某个元素是否为react元素也不可以使用 instanceof 。
          可以使用 $$typeof 是否为  Symbol.for('react.element')。
     *
     * @param {*} type
     * @param {*} props
     * @param {*} key
     * @param {string|object} ref
     * @param {*} owner
     * @param {*} self 当调用React.createElement时,一个*临时*助手,用于检测“this”与“owner”是否不同,以便我们发出警告。我们希望去掉owner并用箭头函数替换字符串ref,只要this和owner相同,行为就不会改变。
     * @param {*} source 表示文件名、行号或其它信息的注释对象(由transpiler或其它方式添加)。
     * @internal
     **/
    

    接下来看正式的代码,整个代码只有一个声明,一个返回,所以只看声明就OK了。

    const element = {
        /*** 翻译: 这个标签允许我们将其唯一地标识为一个React元素 **/
        // This tag allows us to uniquely identify this as a React Element
        $$typeof: REACT_ELEMENT_TYPE,
         /*** REACT_ELEMENT_TYPE = Symbol.for('react.element') **/
    
        /*** 翻译: 属于元素的内置属性 **/
        // Built-in properties that belong on the element
        type: type,
        key: key,
        ref: ref,
        props: props,
    
        /*** 翻译: 记录创建此元素的组件。  **/
        // Record the component responsible for creating this element.
        _owner: owner,
      };
    /*** 中间有一堆开发模式才有代码,这个先无视,下面再讲 **/
    return element;
    

    看到这里,有点轻微懵逼,竟然不是直接返回元素,返回的竟然是个对象,没有任何dom节点返回,设想:这里返回的对象,在经过框架的某个函数,将变成 真实的dom。

    稍微提一下属性修饰词: MDN

    1. 形参1:obj : 要定义属性的对象。
    2. 形参2:prop :要定义或修改的属性的名称或 Symbol
    3. 形参3:desc:要定义或修改的属性描述符。
      | desc描述 | 描述 | 默认值|
      | ----------- | ----------- | -----------|
      | configurable | 该属性的描述符才能够被改变,同时该属性是否可以被删除 | false |
      | enumerable | 是否可枚举,遍历的时候是否显示这个属性 | false |
      | value | 该属性的值 | undefined |
      | writable | value值是否可以被改变 | false|

    看一下开发模式才有的这些属性修饰词吧

    if (__DEV__) {
        /*** 翻译:
    验证标志现在是可变的。我们把它放在一个外部后备存储器上,这样我们就可以冻结整个对象。一旦它们在常用的开发环境中执行(难道是在说变化?),就可以用WeakMap来代替它们。
     **/
        // The validation flag is currently mutative. We put it on
        // an external backing store so that we can freeze the whole object.
        // This can be replaced with a WeakMap once they are implemented in
        // commonly used development environments.
        element._store = {};
    
        /*** 翻译: 
    为了使比较ReactElements更容易用于测试,我们将验证标志设为不可枚举(在可能的情况下,它应该包括我们运行测试的每个环境),因此测试框架忽略它。
    **/
        // To make comparing ReactElements easier for testing purposes, we make
        // the validation flag non-enumerable (where possible, which should
        // include every environment we run tests in), so the test framework
        // ignores it.
        Object.defineProperty(element._store, 'validated', {
          configurable: false,
          enumerable: false,
          writable: true,
          value: false,
        });
        /*** _self属性:不可枚举,不可删除,不可赋值 **/
        // self and source are DEV only properties.
        Object.defineProperty(element, '_self', {
          configurable: false,
          enumerable: false,
          writable: false,
          value: self,
        });
    
        /*** 翻译:
    在两个不同的地方创建的两个元素在测试时应该被认为是相等的,因此我们将其隐藏在枚举中。 **/
        // Two elements created in two different places should be considered
        // equal for testing purposes and therefore we hide it from enumeration.
        Object.defineProperty(element, '_source', {
          configurable: false,
          enumerable: false,
          writable: false,
          value: source,
        });
        /*** 如果可以,冻结 元素和 元素的 props 属性 **/
        if (Object.freeze) {
          Object.freeze(element.props);
          Object.freeze(element);
        }
      }
    

    相关文章

      网友评论

          本文标题:react.createElement方法探究

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