【React】源码遨游(一) createElement()

2021/7/19 1:05:12

本文主要是介绍【React】源码遨游(一) createElement(),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

React源码遨游

今天新开的专栏主要围绕着React源码进行学习,切入口主要围绕着React的顶层API(即React)。

createElement()

createElment() 隶属于顶层ApiReact/src/ReactElement.js中。

首先我们看一下创建组件的代码,然后逐句分析一下。

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 是参数传进来的配置信息。首先看如果这个变量不为空,我们会从配置信息中提取哪些要素:

// 如果配置不为空
if (config != null) {
    // 如果配置中的 ref 有效,则将前面定义的 ref 赋值为配置中的 ref 属性
    if (hasValidRef(config)) {
      ref = config.ref;
      // 开发环境中
      if (__DEV__) {
        // 如果配置中的 ref 属性是一个字符串,则报一个警告(未来不支持字符串传入 ref )
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    // 如果配置中的 key 有效,则将前面定义的 key 赋值为配置中的 key属性
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    // 也是之前定义的 self 和 source, 如果配置中的私有变量 __self, __source不为空,则赋值。
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    // 除了上述提到的属性之外,遍历其余属性,赋值到之前定义的 prop 属性中
    for (propName in config) {
      if ( // 属性名要不是保留字
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        // 满足条件的属性,赋值到 prop 变量中
        props[propName] = config[propName];
      }
    }
  }

接下来处理children参数

// 首先确定子元素的数量,因为 children 不一定只有一个
const childrenLength = arguments.length - 2;
if (childrenLength === 1) 
    // 如果只有一个,那么就把这个 children 赋值给我们定义的 props 变量的 children 属性即可
    props.children = children;
} else if (childrenLength > 1) {
    // 如果大于一个,定义一个children的数组,将所有children都放进去
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
        childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
        // 如果再开发环境,则冻结这个childArray数组(性能提升)
        if (Object.freeze) {
            Object.freeze(childArray);
        }
    }
    // 将子元素数组赋值到 props 变量的 children 属性
    props.children = childArray;
}

接下来我们来处理第一个参数type

// Resolve default props
  // 如果 type 参数中的 defaultProps属性不为空
  if (type && type.defaultProps) {
	// 新建一个 defaultProps 变量,赋值为参数 type 的 defaultProps
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      // 如果之前我们定义的 props 中有属性为 undefined,则我们从defaultProps中提取默认值
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    // 如果 key 或者 ref 不为空
    if (key || ref) {
      // 如果传入的 type 是一个函数,则显示名设置为函数的显示名、名、未知。
      // 否则就显示 type 名
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) { // 如果 key 属性不为空
        // 如果通过 props 对象获取 key 时报错
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) { // 如果 ref 不为空
        // 如果通过 props 对象获取 ref 时报错
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 返回 ReactElement
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

看一下最后返回的ReactElement:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 这个type能让我们判断这个元素是一个react元素
    $$typeof: REACT_ELEMENT_TYPE,

    // 元素拥有的内构属性的值
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 记录创建元素的拥有者
    _owner: owner,
  };

  if (__DEV__) { // 开发模式
    // validated是可更改的,所以用一个内部私有变量_store储存,从而可以冻结整个对象。
    // 未来可以用一个WeakMap对象来代替
    element._store = {};

    // 在开发模式下,把validated设置为不可遍历,使测试更简单
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // _self和_source是内部私有变量
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // 在测试环境中不同地方创建的元素应被认为平等,所以将_source定义为不可遍历
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      // 同样的,为了性能,冻结元素
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
  // 返回我们定义的元素
  return element;
};

我们看看ReactElement.js里面的其他方法,说明都在注释中:

function hasValidRef(config) {
  if (__DEV__) {
    // 如果是开发模式,查看 config 中定义的 get 方法, 如果 get 中的 isReactWarning
    // 为真(说明该 ref 有警告,在开发模式不有效,返回false
    if (hasOwnProperty.call(config, 'ref')) {
      const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  // 如果不是开发模式,则直接判断 ref 是否为空
  return config.ref !== undefined;
}
// key 的验证方式和上述是一样的
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;
}
// 定义属性的get方法
function defineKeyPropWarningGetter(props, displayName) {
  // 这个变量的意思是访问Key的时候是否有警告
  const warnAboutAccessingKey = function() {
    if (__DEV__) {
      // 如果特殊属性key的警告没有显示过
      if (!specialPropKeyWarningShown) {
        // 将这个flag先置为true
        specialPropKeyWarningShown = true;
        // 在开发者工具中输出error
        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,
        );
      }
    }
  };
  // 将这个属性的 isReactWarning 置为 true
  warnAboutAccessingKey.isReactWarning = true;
  // 用对象的 defineProperty 方法,定义key的属性,get方法设置为 warnAboutAccessingKey
  Object.defineProperty(props, 'key', {
    get: warnAboutAccessingKey,
    configurable: true,
  });
}
// 定义 ref 的get方法也是一样的。
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,
  });
}
// 创建一个方法,生产提供类型的ReactElement
export function createFactory(type) {
  const factory = createElement.bind(null, type);
  // 暴露Factory的type属性,这样就能轻松访问type属性。
  // 这不是一个构造方法
  factory.type = type;
  return factory;
}
// 克隆元素,并且替换一个新的key
export function cloneAndReplaceKey(oldElement, newKey) {
  const newElement = ReactElement(
    oldElement.type,
    newKey,
    oldElement.ref,
    oldElement._self,
    oldElement._source,
    oldElement._owner,
    oldElement.props,
  );

  return newElement;
}
// 克隆元素方法
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;

  // 传入原始元素
  const props = Object.assign({}, element.props);

  // 下面和createElement的处理一致
  let key = element.key;
  let ref = element.ref;
  // self 属性设置为元素的_self,因为owner保持为原来的owner不变
  const self = element._self;
  // source设置为克隆元素的_souce,以更好的溯源
  const source = element._source;

  // Owner也保留之前的owner,而不是被克隆的元素
  let owner = element._owner;

  if (config != null) {
    if (hasValidRef(config)) {
      // 从配置中获取 ref, 并且将owner设置为 ReactCurrentOwner.current
      ref = config.ref;
      owner = ReactCurrentOwner.current;
    }
    // 下述操作和createElement一致
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    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) {
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

  // 下述操作和createElement一致
  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);
}


这篇关于【React】源码遨游(一) createElement()的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程