React源码解析————ReactElement.js

2021SC@SDUSC

2021SC@SDUSC

ReactElement.js

总览

ReactElement.js内部函数有:

│   ├── hasValidRef
│   ├── hasValidKey
│   ├── defineKeyPropWarningGetter
│   ├── defineRefPropWarningGetter 
│   ├── warnIfStringRefCannotBeAutoConverted
│   ├── ReactElement

export的函数有:

│   ├── jsx
│   ├── jsxDEV
│   ├── createElement 
│   ├── createFactory
│   ├── cloneAndReplaceKey 
│   ├── cloneElement
│   ├── isValidElement

jsx和jsxDEV这两个函数的代码都可以基本归入createElement中,所以我会在createElement中一并讲解而不会单独分出一部分。

相关js函数

Object.getOwnPropertyDescriptor():
返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)ex:

const object1 = {
  property1: 42
};
const descriptor1 = Object.getOwnPropertyDescriptor(object1, 'property1');
console.log(descriptor1.configurable);
// expected output: true
console.log(descriptor1.value);
// expected output: 42

hasOwnProperty():
返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。ex:

const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1'));
// expected output: true
console.log(object1.hasOwnProperty('toString'));
// expected output: false
console.log(object1.hasOwnProperty('hasOwnProperty'));
// expected output: false

Object.defineProperty():
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。ex:

const object1 = {};
Object.defineProperty(object1, 'property1', {
  value: 42,
  writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42

hasValidRef

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

通过Ref属性的取值器对象的isReactWarning属性检测是否含有合法的Ref,在开发环境下,如果这个props是react元素的props那么获取上面的ref就是不合法的,因为在creatElement的时候已经调用了defineRefPropWarningGetter。生产环境下如果config.ref !== undefined,说明合法。

hasValidKey

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

key 的验证方式和上述是一样的

defineKeyPropWarningGetter

function defineKeyPropWarningGetter(props, displayName) {
  const warnAboutAccessingKey = function() {
    if (__DEV__) {
      if (!specialPropKeyWarningShown) {
        specialPropKeyWarningShown = true;
        console.error(
          '%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://reactjs.org/link/special-props)',
          displayName,
        );
      }
    }
  };
  warnAboutAccessingKey.isReactWarning = true;
  Object.defineProperty(props, 'key', {
    get: warnAboutAccessingKey,
    configurable: true,
  });
}

开发模式下,该函数在creatElement函数中可能被调用。锁定props.key的值使得无法获取props.key,标记获取props中的key值是不合法的,当使用props.key的时候,会执行warnAboutAccessingKey函数,进行报错,从而获取不到key属性的值。
即如下调用始终返回undefined
ex:props.key
给props对象定义key属性,以及key属性的取值器为warnAboutAccessingKey对象
该对象上存在一个isReactWarning为true的标志,在hasValidKey上就是通过isReactWarning来判断获取key是否合法
specialPropKeyWarningShown用于标记key不合法的错误信息是否已经显示,初始值为undefined。

defineRefPropWarningGetter

function defineRefPropWarningGetter(props, displayName) {
  const warnAboutAccessingRef = function() {
    if (__DEV__) {
      if (!specialPropRefWarningShown) {
        specialPropRefWarningShown = true;
        console.error(
          '%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://reactjs.org/link/special-props)',
          displayName,
        );
      }
    }
  };
  warnAboutAccessingRef.isReactWarning = true;
  Object.defineProperty(props, 'ref', {
    get: warnAboutAccessingRef,
    configurable: true,
  });
}

逻辑与defineKeyPropWarningGetter一致,锁定props.ref的值使得无法获取props.ref,标记获取props中的ref值是不合法的,当使用props.ref的时候,会执行warnAboutAccessingKey函数,进行报错,从而获取不到ref属性的值。

warnIfStringRefCannotBeAutoConverted

didWarnAboutStringRefs = {};
function warnIfStringRefCannotBeAutoConverted(config) {
  if (__DEV__) {
    if (
      typeof config.ref === 'string' &&
      ReactCurrentOwner.current &&
      config.__self &&
      ReactCurrentOwner.current.stateNode !== config.__self
    ) {
      const componentName = getComponentNameFromType(
        ReactCurrentOwner.current.type,
      );

      if (!didWarnAboutStringRefs[componentName]) {
        console.error(
          'Component "%s" contains the string ref "%s". ' +
            'Support for string refs will be removed in a future major release. ' +
            'This case cannot be automatically converted to an arrow function. ' +
            'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
            'Learn more about using refs safely here: ' +
            'https://reactjs.org/link/strict-mode-string-ref',
          componentName,
          config.ref,
        );
        didWarnAboutStringRefs[componentName] = true;
      }
    }
  }
}

在开发环境下,如果配置中的 ref 属性是string,且ReactCurrentOwner的current和config的_self不为空且ReactCurrentOwner的stateNode和config的_self不相等,下面是ReactCurrentOwner以及其使用的Fiber的相关属性信息:

const ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Fiber),
};
export type Fiber = {|
  tag = WorkTag,
  type: any,
  stateNode: any,
|};
 export type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24;

则调用getComponentNameFromType,该函数在开发环境下type:any且tag是number时报一个警告,声明这是一个unexpected的调用,:

if (__DEV__) {
    if (typeof (type: any).tag === 'number') {
      console.error(
        'Received an unexpected object in getComponentNameFromType(). ' +
          'This is likely a bug in React. Please file an issue.',
      );
    }
  }

同时继续向下,接着报一个警告(因为未来不支持字符串传入 ref )

ReactElement

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

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

  if (__DEV__) {
    // 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 = {};

    // 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 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,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

被createElement函数调用,根据环境设置对应的属性。
代码性能优化:为提高测试环境下,element比较速度,将element的一些属性配置为不可数(enumerable: false),for…in还是Object.keys都无法获取这些属性,提高了速度。开发环境比生产环境多了_store,_self,_source属性,并且props以及element被冻结,无法修改配置。
$$typeof: REACT_ELEMENT_TYPE,能让我们判断这个元素是一个react元素

createElement

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;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;

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

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    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.
  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];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  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);
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

config 是参数传进来的配置信息,如果配置不为空:
1.如果配置中的 ref 有效,则将前面定义的 ref 赋值为配置中的 ref 属性
2.如果配置中的 key 有效,则将前面定义的 key 赋值为配置中的 key属性
3.如果配置中的私有变量 __self, __source不为空,则赋值。
除了上述提到的属性之外,遍历其余属性,赋值到之前定义的 prop 属性中
然后确定子元素的数量,因为 children 不一定只有一个,分情况讨论:
1.如果只有一个,那么就把这个 children 赋值给我们定义的 props 变量的 children 属性即可
2.如果大于一个,定义一个children的数组,将所有children都放进去,再将子元素数组赋值到 props 变量的 children 属性(开发环境,则冻结这个childArray数组(提升性能))
然后确定 type 参数中的 defaultProps属性是否为空,若不为空,如果之前我们定义的 props 中有属性为 undefined,则我们从defaultProps中提取默认值
开发环境下,若key或ref不为空,调用defineKeyPropWarningGetter或defineRefPropWarningGetter

createFactory

export function createFactory(type) {
  const factory = createElement.bind(null, type);
  // Expose the type on the factory and the prototype so that it can be
  // easily accessed on elements. E.g. `<Foo />.type === Foo`.
  // This should not be named `constructor` since this may not be the function
  // that created the element, and it may not even be a constructor.
  // Legacy hook: remove it
  factory.type = type;
  return factory;
}

返回一个函数,该函数生成给定类型的 React 元素。
用于将在字符串或者函数或者类转换成一个react元素,该元素的type为字符串或者函数或者类的构造函数

cloneAndReplaceKey

export function cloneAndReplaceKey(oldElement, newKey) {
  const newElement = ReactElement(
    oldElement.type,
    newKey,
    oldElement.ref,
    oldElement._self,
    oldElement._source,
    oldElement._owner,
    oldElement.props,
  );

  return newElement;
}

从一个旧的react元素身上clone它的type , ref, _self , _scource , _owner , props,得到的新的react元素并且设置新的key

cloneElement

export function cloneElement(element, config, children) {
  invariant(
    !(element === null || element === undefined),
    'React.cloneElement(...): The argument must be a React element, but you passed %s.',
    element,
  );

  let propName;

  // Original props are copied
  const props = Object.assign({}, element.props);

  // Reserved names are extracted
  let key = element.key;
  let ref = element.ref;
  // Self is preserved since the owner is preserved.
  const self = element._self;
  // Source is preserved since cloneElement is unlikely to be targeted by a
  // transpiler, and the original source is probably a better indicator of the
  // true owner.
  const source = element._source;

  // Owner will be preserved, unless ref is overridden
  let owner = element._owner;

  if (config != null) {
    if (hasValidRef(config)) {
      // Silently steal the ref from the parent.
      ref = config.ref;
      owner = ReactCurrentOwner.current;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    // Remaining properties override existing props
    let defaultProps;
    if (element.type && element.type.defaultProps) {
      defaultProps = element.type.defaultProps;
    }
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        if (config[propName] === undefined && defaultProps !== undefined) {
          // Resolve default props
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

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

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

使用 element 作为起点,克隆并返回一个新的 React 元素。 所产生的元素将具有原始元素的 props ,新的 props 为浅层合并。 新的子元素将取代现有的子元素, key 和 ref 将被保留。
React.cloneElement() 几乎等效于:

<element.type {...element.props} {...props}>{children}</element.type>

然而,它也会保留 ref 。这意味着,如果你通过它上面的 ref 获取自己的子节点,你将不会有机会从你的祖先获取它。你只会获得绑定在你的新元素上的相同ref 。

isValidElement

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

判断一个对象是否是合法的react元素,即判断其$$typeof属性是否为REACT_ELEMENT_TYPE

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值