资讯专栏INFORMATION COLUMN

ReactElement源码解析

mumumu / 1027人阅读

摘要:在源码添加的注释在。解析的主要调用如下当然在下还有些其他的调用。此外,冻结一个对象后该对象的原型也不能被修改。方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。

前言

ReactElement并不像之前所谈的PureComponent和Component那样被频繁的显示使用,但我估计他应该是在react暴露出的api中被调用最为频繁的,关于此看完后面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五个方法,总共400来行代码,比较容易。

文章中如有不当之处,欢迎交流指点。react版本16.8.2。在源码添加的注释在githubreact-source-learn。
jsx与ReactElement

在使用react时我们经常在render方法返回(函数组件的话可能是直接返回)类似下面的代码。


    

测试

这就是传说中的jsx语法,js并没有这种东西,这种语法最终都会被转换成标准的js。请看下图:

发现这些jsx被转化成了js,每个组件或者html标签转化后都是调用React.createElement(type, config, children)。这里的React.createElement其实就是ReactElement.createElement。由此可以推测,ReactElement暴露的方法是调用最频繁的。

createElement解析

createElement的主要调用如下:

createElement -> ReactElement

当然在dev下还有些其他的调用。

createElement源码如下

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

 // jsx转换后调用的方法
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 (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
    // 将config中的数据放到props中,  key,ref,__self,__source除外
    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生成
  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
  // 复制默认props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 这里不然从props中读key, 和ref, 但是里边事实上就是没有的
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === "function"
          ? type.displayName || type.name || "Unknown"
          : type;
      if (key) {
        // displayName: 构造函数名, 或标签名 a , h1
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 就一个普通对象
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

createElement主要做了如下事情:

将特殊属性从config取出, 如key,ref,__self,__source

将非特殊属性挂到props上,比如上边那个图中的className

将第三个及之后的参数挂到props.children上,多个是生成数组,单个是直接挂

默认值defaultProps的处理

将处理好的数据作为参数调用ReactElement并返回

ReactElement源码如下

// 这个函数做的事非常简单, 就是将传进来的参放到一个对象里边返回
 // 其中source, self在生产模式没有返回
 // owner 变成了_owner
 // 开发模式下, 返回了将source, self也挂在了返回的对象上, 变成了_source, _self
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, // 一个Symobol或者16进制数,
                                  //用于表示ReactElement类型

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

  // 这里边放了self, source
  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,
    });

    // Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;
    // 冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、
    // 可配置性、可写性,以及不能修改已有属性的值。
    // 此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。


    // Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。

    // Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。


    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

他几乎是没做什么事情的,就是将传入的参数放到一个对象返回,加了一个$$typeof标识ReactElement。其中使用了一个Object.freeze方法,这个方法不太常用,意思是冻结一个对象,使其不能被修改,相关的还有Object.seal,Object.preventExtensions,可以找些文档了解下。

小结下ReactElement.createElement

ReactElement.createElement最终返回的是一个普通的对象,对参数进行了校验,提取等操作。上面为解析dev下的代码,去看一下会发现也是比较有趣的。

createFactory,cloneAndReplaceKey,cloneElement和isValidElement 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. `.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;

  // 这样
  // return function factory(...args) {
  //   return createElement(type, ...args);
  // }
}

这个方法很简单,是对createElement的一个柯里化的操作。

cloneAndReplaceKey
// 克隆reactElement并将key改为新key
export function cloneAndReplaceKey(oldElement, newKey) {
  const newElement = ReactElement(
    oldElement.type,
    newKey,
    oldElement.ref,
    oldElement._self,
    oldElement._source,
    oldElement._owner,
    oldElement.props,
  );

  return newElement;
}

从旧的ReactElement对象生成一个新的,将key属性替换成新的

isValidElement
/**
 * Verifies the object is a ReactElement.
 * See https://reactjs.org/docs/react-api.html#isvalidelement
 * @param {?object} object
 * @return {boolean} True if `object` is a ReactElement.
 * @final
 */
export function isValidElement(object) {
  // 还是很严谨的
  return (
    typeof object === "object" &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

判断一个值是不是ReactElement,使用了创建时挂上去的$$typeof

cloneElement
// 和createElement基本相同
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);
}

这段代码和createElement非常相似,不同之处在于他是返回第一个参数ReactElement的一个副本。他的key,ref等属性和提供的需要被克隆的ReactElement的相同,props也是原来的props,但是可以传入config修改。

/packages/react.js小结

至此,/packages/react.js总的最最重要的东西已经分析完了,关于hooks等其他内容就像不分析了。这里边的代码其实并没有做什神奇的事情,ReactElement只是创建和操作普通对象,Component和PureComponent只是定义了两个简单的构造函数,定义了几个方法,其中比较重要的应该是updater,但是到目前为止还没有看到他的身影。这些东西都不涉及dom操作,是平台无关的。

这里代码都比较好理解,后面就将进入深水区了,要开始研究ReactDOM里边的render了。加油!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103429.html

相关文章

  • React源码解析之React.createElement()和ReactElement()

    摘要:一语法转换到语法从转换到会用到,所以先熟悉下到的转换。对于库作者而言,冻结对象可防止有人修改库的核心对象。 showImg(https://segmentfault.com/img/remote/1460000019757204); 一、JSX语法转换到Js语法从 JSX 转换到 JS 会用到React.createElement(),所以先熟悉下 JSX 到 JS 的转换。 这边是 ...

    BlackMass 评论0 收藏0
  • React源码解析ReactDOM.render源码

    摘要:的创建组件,其实根源还是调用了编译之后一般写法建议用来进行源码的跟踪链接从源码角度来看创建一个组件的过程中发生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...

    joywek 评论0 收藏0
  • React源码解析之React.children.map()

    摘要:一例子看到一个有趣的现象,就是多层嵌套的数组经过后,平铺成了,接下来以该例解析二作用源码进行基本的判断和初始化后,调用该方法就是重命名了,即解析注意,该数组在里面滚了一圈后,会结果三作用的包裹器源码第一次第二次如果字符串中有连续多个的话 showImg(https://segmentfault.com/img/remote/1460000019968077?w=1240&h=698);...

    kuangcaibao 评论0 收藏0
  • ReactDOM.render源码解析-1

    摘要:本文将对源码做一个初步解析。首先在方法中校验参数是否合法,然后调用在中,调用拿到了的一个实例,调用拿到了,用于注入到,和作为返回值,调用开始调度过程在中,首先清理了中的所有子节点,然后了一个并返回是如何调度的是一个什么样的类的操作是在哪里 初步看了react-dom这个包的一些源码,发现其比react包要复杂得多,react包中基本不存在跨包调用的情况,他所做的也仅仅是定义了React...

    _Zhao 评论0 收藏0
  • React前端学习小结

    摘要:正式开始系统地学习前端已经三个多月了,感觉前端知识体系庞杂但是又非常有趣。更新一个节点需要做的事情有两件,更新顶层标签的属性,更新这个标签包裹的子节点。 正式开始系统地学习前端已经三个多月了,感觉前端知识体系庞杂但是又非常有趣。前端演进到现在对开发人员的代码功底要求已经越来越高,几年前的前端开发还是大量操作DOM,直接与用户交互,而React、Vue等MVVM框架的出现,则帮助开发者从...

    iOS122 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<