React 深度学习:React Core—ReactElement

董翰墨
2023-12-01

path:packages/react/src/ReactElement.js

ReactElement 下面导出了多个方法:

  • createElement 用于创建并返回一个新的ReactElement 元素
  • createFactory 用于返回固定类的 createElement 方法 【已废弃】
  • cloneElement 克隆一个元素
  • isValidElement 验证一个对象是否为 ReactElement 。
  • cloneAndReplaceKey 使用给定 key 返回一个新的 ReactElement 类型

ReactElement

ReactElement 是用来创建 React 元素的工厂方法。

ReactElemen 不再遵循类的模式,并未从 ReactElement 模块导出,因此不能使用 new 来调用它,并且无法使用 instanceOf 检查。 取而代之,可以检测 $$typeof 字段与 Symbol.for('react.element') 是否匹配来判断是否是一个 React 元素

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__) {
    // do something
  }

  return element;
};
复制代码

从上面的源码中可以看到 React 元素是一个拥有以下属性的普通的对象:

  • $$typeof

    这个标记允许我们唯一地将其标识为 React 元素,其值为 REACT_ELEMENT_TYPE

    我们可以通过判断对象的 $$typeof 是否等于 REACT_ELEMENT_TYPE 来判断其是否为一个 React 元素。

  • type

  • key

  • ref

  • props

  • _owner

createElement

React.createElement 函数在 React 中具有举足轻重的地位,我们的组件最后都会编译成它。

React.createElement 使用给定的 type 创建并返回的新的 React 元素,参见 React 官方文档

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;
  
  // 不为 undefined 和 null
  if (config != null) {
    // config 是否含有有效的 ref 属性
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    
    // config 是否含有有效的 key 属性
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 剩余的属性被添加到一个新的 props 对象中
    // RESERVED_PROPS 包含四个属性 ref、key、__self、__source
    // 这里就是拷贝除这四个属性之外的其他属性
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
  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__) {
      // do somthing
    }
    props.children = childArray;
  }

  // 解析默认 props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    // do something
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
复制代码

示例

来看看一个示例:

React.createElement('div');
复制代码

返回一个对象,对象如下:

可以看到句代码创建了一个普通的 javascript 对象。这时候,产生了疑问:

  • DOM 层级如此之多,信息之复杂,React 又是如何实现的?

React 如何实现 DOM 层级

来看下面的 Hello 组件:

function Hello(props) {
  function sayHi() { return (<div>Hi</div>); }
  
  function handleClick(e) {
    console.log('click');
  }
  return (
    <div>
      { sayHi() }
      Hello { props.name }
      <p className="title" onClick={ handleClick }>React</p>
      <p className="content">React is cool!</p>
      <MyFooter className="footer">
        <div className="concat">concat</div>
        <div className="info">company info</div>
      </MyFooter>
    </div>
  );
}
复制代码

MyFooter 组件:

function MyFooter(props) {
  return (
    <div className="footer">
      { props.children }
    </div>
  );
}
复制代码

我们采用了函数式组件的方式,他们将分别被编译为:

// Hello 组件
function Hello(props) {
  function sayHi() {
    return React.createElement("div", null, "Hi");
  }
  
  function handleClick(e) {
    console.log('click');
  }
  
  return React.createElement("div", null, sayHi(), "Hello ", props.name, React.createElement("p", {
    className: "title",
    onClick: handleClick
  }, "React"), React.createElement("p", {
    className: "content"
  }, "React is cool!"), React.createElement(MyFooter, {
    className: "footer"
  }, React.createElement("div", {
    className: "concat"
  }, "concat"), React.createElement("div", {
    className: "info"
  }, "company info")));
}

// MyFooter 组件
function MyFooter(props) {
  return React.createElement("div", {
    className: "footer"
  }, props.children);
}
复制代码

首先,从上面的代码我们可以看出下面几点:

  • 组件都会被编译成 React.createElement 不论是自定义组件还是原始的 html 标签,都会被编译器编译。

  • React.createElement 方法的参数个数是可变的,在上面源码分析中,我们已经看到从第三个参数开始的所有参数会打包为一个数组,存入 React 元素的 props 属性的 children 中。

  • 不论从组件的哪一级部分开始划分,其子元素都是通过函数参数传递进父级的,并最后都会存放于 props 属性的 children 中。

  • 在处理 children 时,编译器会很智能地区分字符串、{} 表达式和 JSX,不同的部分都会被转换成一个 React.createElement 的参数,因此在上面的代码中,会产生 6 个参数:

    • sayHi()
    • "Hello"
    • props.nameReact.createElement(...)
    • React.createElement(...)
    • React.createElement(...)
  • 自定义组件的 type 参数值是组件的直接引用,而不是组件名的字符串。MyFooter 组件的 type 属性是一个函数,是它本身,而不是 "MyFooter" 字符串。

  • 只要是写在 JSX 上的属性,都被当做 config 的一部分传递给了 React.createElement() ,包括事件,例如这里的 onClick

再看看生成的 JavaScript 对象:

所谓的层级结构就是通过 React 元素的 props 属性中的 children 属性层层嵌套得来的。

createFactory

此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。

cloneAndReplaceKey

使用新的 key 克隆一个 React 元素。

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

  return newElement;
}
复制代码

cloneElement

克隆一个旧的 react 元素,并返回一新的个 React 元素,参见 React 官方文档


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;

  // 复制原始 props
  const props = Object.assign({}, element.props);

  // 提取保留名称
  let key = element.key;
  let ref = element.ref;
  // Self 被保存,因为 owner 被保存。
  const self = element._self;
  // 保存 Source,因为 cloneElement 不太可能被 transpiler 定位,
  // 而原始源 source 可能是真正 owner 的更好指示器。
  const source = element._source;

  // Owner 将被保留,除非 ref 被覆盖
  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;
    }
    
    // 其余属性覆盖现有的 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
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

  // Children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
  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);
}
复制代码

cloneElement 方法会拷贝对旧 React 元素的属性进行拷贝,并使用新传入的 configchildren 参数对这些属性进行更新。然后使用这些新的属性作为参数调用 ReactElement 构造方法,生成并返回新的 React 元素。

isValidElement

通过判断对象的 $$typeof 属性与 Symbol.for('react.element') 是否相同来,验证一个对象是否为 React 元素。

通过 React.createElement 方法创建的对象,都含有一个值完全相同的 $$typeof 属性,标识其为一个 React 元素。

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

其他

RESERVED_PROPS

保留属性

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
复制代码

hasValidRef

hasValidRef 判断一个对象中是否含有 ref 属性

function hasValidRef(config) {
  if (__DEV__) {
    // do something
  }
  return config.ref !== undefined;
}
复制代码

hasValidKey

hasValidKey 判断一个对象中是否含有 key 属性

function hasValidKey(config) {
  if (__DEV__) {
    // do something
  }
  return config.key !== undefined;
}
复制代码

遗留问题

  • 普通的 javascript 对象(React 元素)是如何变成了我们页面的 DOM 结构的

写文章的时候再阅读 React 的官方文档,发现这份文档制作很用心,循序渐进,由浅入深。

借用文档的一句话:

我们将在下一章节中探讨如何将 React 元素渲染为 DOM。


转载于:https://juejin.im/post/5d10f2c06fb9a07eb15d62af

 类似资料: