React 深度学习:4. 创建 DOM 元素

本文介绍了React初次渲染时创建DOM元素的过程,包括createElement、createTextNode、setInitialProperties等方法的详细作用。讨论了如何通过递归方式构建DOM树,并解释了React如何处理属性和事件,特别是对html字符串的特殊处理。同时提到了元素更新阶段的相关方法,如updateProperties和diffProperties。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React 初次渲染的时候无非会经历如下四个步骤:


ReactElement 元素的创建,以及它如何表达嵌套在上个章节中也已阐述。React 的难点就在于如何进行数据处理,并且保持高效,我们暂且忽略它。秉持由浅入深的原则进行学习。

想要从 ReactElement 元素的层层嵌套结构中解析出 DOM,能直接想到的办法就是使用递归。在遇到每一层的时候根据元素类型创建不同的 DOM 元素,最后将这一个庞大的 DOM 元素树插入到真实的 DOM 中。

这些功能,都集中在:

packages/react-dom/src/client/ReactDOMComponent.js

createElement

/**
 * 创建元素
 * @param type
 * @param props
 * @param rootContainerElement
 * @param parentNamespace
 * @returns {Element}
 */
export function createElement(
  type: string,
  props: Object,
  rootContainerElement: Element | Document,
  parentNamespace: string,
): Element {
  let isCustomComponentTag;

  // 我们在它们的父容器的命名空间中创建标记,除了 HTML 标签没有命名空间。
  // getOwnerDocumentFromRootContainer 方法用来获取 document
  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  if (namespaceURI === HTML_NAMESPACE) {

    if (type === 'script') {
      // 通过 .innerHTML 创建脚本,这样它的 “parser-inserted” 标志就被设置为true,而且不会执行
      const div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
      // 这将保证生成一个脚本元素。
      const firstChild = ((div.firstChild: any): HTMLScriptElement);
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      // $FlowIssue `createElement` 应该为 Web Components 更新
      domElement = ownerDocument.createElement(type, {
    is: props.is});
    } else {
      // 因为 Firefox bug,分离 else 分支,而不是使用 `props.is || undefined`
      // 参见 https://github.com/facebook/react/pull/6896
      // 和 https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
      domElement = ownerDocument.createElement(type);
      // 通常在 `setInitialDOMProperties` 中分配属性,
      // 但是 `select` 上的 `multiple` 和 `size` 属性需要在 `option` 被插入之前添加。
      // 这可以防止:
      // 一个错误,其中 `select` 不能滚动到正确的选项,因为单一的 `select` 元素自动选择第一项 #13222
      // 一个 bug,其中 `select` 将第一个项目设置为 selected,而忽略 `size` 属性 #14239
      // 参见 https://github.com/facebook/react/issues/13222
      // 和 https://github.com/facebook/react/issues/14239
      if (type === 'select') {
        const node = ((domElement: any): HTMLSelectElement);
        if (props.multiple) {
          node.multiple = true;
        } else if (props.size) {
          // 设置大于 1 的 size 会使 select 的行为类似于 `multiple=true`,其中可能没有选择任何选项。
          // select 只有在“单一选择模式”下才需要这样做。
          node.size = props.size;
        }
      }
    }
  } else {
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }

  return domElement;
}复制代码

createElement 方法会根据不同的类型创建不同的 DOM 元素。值得指出的是,它不仅支持原生的 html 标签,还支持 WebComponent。

createTextNode

export function createTextNode(
  text: string,
  rootContainerElement: Element | Document,
): Text {
  return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
    text,
  );
}复制代码

createTextNode 方法创建了一个文本节点

setInitialProperties

export function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  const isCustomComponentTag = isCustomComponent(tag, rawProps);

  // TODO: Make sure that we check isMounted before firing any of these events.
  let props: Object;
  switch (tag) {
    case 'iframe':
    case 'object':
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'video':
    case 'audio':
      // Create listener for each media event
      for (let i = 0; i < mediaEventTypes.length; i++) {
        trapBubbledEvent(mediaEventTypes[i], domElement);
      }
      props = rawProps;
      break;
    case 'source':
      trapBubbledEvent(TOP_ERROR, domElement);
      props = rawProps;
      break;
    case 'img':
    case 'image':
    case 'link':
      trapBubbledEvent(TOP_ERROR, domElement);
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'form':
      trapBubbledEvent(TOP_RESET, domElement);
      trapBubbledEvent(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值