React 架构 Render 阶段 第二篇

429 阅读3分钟

上一篇主要是说流程,大概的流程是什么样子的,经历了哪些函数。

本篇开始要具体讲解JSX。如有不对之处还望指出,共同进步,谢谢~

深入理解JSX

JSX对于开发人员应该并不陌生,React也是采用JSX这种方式进行处理、渲染的。

接下来我们思考下面这些问题:

  • JSX与Fiber的关系
  • React Component 与 React Element之间有什么关系
  • React Component、React Element 与 JSX 有什么关系

bable 来看看吧

image.png

能看到,JSX在React中被编译成 React.createElement 方法的执行。我们来看看 createElement 方法中具体做了什么

createElement

在调用 React.createElement 时,调用的是文件 react/packages/react/src/ReactElement.js 下的 createElement:

export function createElement(type, config, children) { ... }

参数对应:

  • type: 'h3'
  • config: null
  • children: 'hello'

这里的config指JSX对象的属性,比如给h3加入title属性:

image.png

如果h3里面还有一个div,第四个参数就会再次调用 React.createElement:

image.png

接下来看 React.createElement 方法都做了什么:

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
}
export function createElement(type, config, children) {
  // __DEV__ 是用来判断 开发环境 还是 生产环境
  // true 是开发环境,false 是生产环境
  let propName;

  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // title等属性,不为null
  if (config != null) {
    // config 是存在ref
    if (hasValidRef(config)) {
      ref = config.ref;

      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    // key 是否存在
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    // self和source的处理
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    // 遍历 config
    for (propName in config) {
      // 除了原型上的属性 和 key ref __self __source 属性,剩下的都放到props属性中
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  // 参数数量,length可能是3,也可能是4
  const childrenLength = arguments.length - 2;
  // 如果是3个参数,props.children直接等于children,hello没有兄弟div的时候。
  // 如果是4个参数,props.children被赋值childArray
  // childArray 是通过对第四个参数的遍历生成从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];
    }
    // 冻结 childArray 数组,不允许再被修改
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    // childArray 赋值给 props.children
    props.children = childArray;
  }
  // type 中是否有默认 defaultProps属性,如果有 遍历给到 props 生成对应的属性
  // classComponent 会成为该函数的 type 参数,所以 defaultProps 是 classComponent 的属性。
  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);
      }
    }
  }
  // 最后返回一个 element
  // ReactElement 会返回一个 element
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

你可能会问,bable中有四个参数,但是方法接受三个参数,没关系,你看这段源码里面有arguments,它是通过arguments处理的。

注意:一个classComponent会被当作type参数被传入给 createElement,也就是它的第一个参数。

上面的代码最后会返回ReactElement函数返回的element:

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
  // 开发环境会进入这里,生产环境不会进入
  // 这段略过,不需要了解
  if (__DEV__) {
    element._store = {};
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
  // 最后返回的就是 element
  return element;
};

我们在函数中看到这样一个值:REACT_ELEMENT_TYPE,这个值也出现在 isValidElement,这个函数用来判断什么样的对象是合法的 element。

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

满足return的判断就是合法的element。

还记得我们最初的问题么:

  • React Component 与 React Element之间有什么关系?

    React Component是React.createElement 方法的第一个参数参数。React Element就是通过React.createElement 方法调用之后返回的结果。

  • JSX与Fiber之间有什么关系?

    首屏渲染时会创建workerInProgress Fiber树,然后会在根节点RootFiber下创建Fiber节点,每个节点其实就是JSX对象。更新时会存在一个current Fiber树,所以在之后生成的workerInProgress Fiber树节点会将组件返回的JSX对象与组件返回的current Fiber节点做对比,根据对比的结果生成workerInProgress Fiber树。

    JSX最后会被编译为React.createElement方法的执行,那么理论上来说,只要改变React.createElement方法,就能改变页面上渲染的结果。

总结

本篇文章主要是深入讲解JSX在React中是如何编译的,以及React Component、React Element、JSX之间有什么关系。

接下来会进入mount流程,首屏渲染的具体流程。谢谢大家支持