# diffProperties

这是对 DOM 节点属性是否有变化的判断,对于以下节点,不管是否有变化,updatePayload都至少是一个空数组,也就是需要重新渲染

  • input
  • option
  • select
  • textarea

之后进入两个循环

# 第一个循环主要判断被删除的props

if (
  nextProps.hasOwnProperty(propKey) ||
  !lastProps.hasOwnProperty(propKey) ||
  lastProps[propKey] == null
) {
  continue
}

这边的条件是:

  • 如果新的props中有属性
  • 或者老的props中没有这个属性
  • 或者老的prop等于null

那么这个不是被删除的

如果找到被删除的属性,对于一些特殊属性需要执行操作,最明显的是style,需要把所有style属性都清空成空字符串

# 第二个循环就是找不同了

const nextProp = nextProps[propKey]
const lastProp = lastProps != null ? lastProps[propKey] : undefined
if (
  !nextProps.hasOwnProperty(propKey) ||
  nextProp === lastProp ||
  (nextProp == null && lastProp == null)
) {
  continue
}

条件如下:

  • 如果新的没有
  • 或者新老属性相同
  • 或者都等于null

这代表是相同的。对于style因为传入的是object,所以肯定不相同(除非开发者自己做了优化),所以需要等style中每个属性进行判断是否变化,并且找出变化的集合

对于DANGEROUSLY_SET_INNER_HTML,需要判断对象内的__html是否变化

registrationNameModules是事件相关的,默认只是空对象,React 有一套注入机制,讲event的时候再详细讲解。

# 小结

我们会发现最后返回的数组会是这个样子的:

updatePayload = [key1, value1, key2, value2]

completeWork阶段,是通过

if (updatePayload) {
  markUpdate(workInProgress)
}

来判断是否需要更新的,所以即便返回空数组还是会需要更新

export function diffProperties(
  domElement: Element,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
  rootContainerElement: Element | Document,
): null | Array<mixed> {
  if (__DEV__) {
    validatePropertiesInDevelopment(tag, nextRawProps)
  }

  let updatePayload: null | Array<any> = null

  let lastProps: Object
  let nextProps: Object
  switch (tag) {
    case 'input':
      lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps)
      nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps)
      updatePayload = []
      break
    case 'option':
      lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps)
      nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps)
      updatePayload = []
      break
    case 'select':
      lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps)
      nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps)
      updatePayload = []
      break
    case 'textarea':
      lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps)
      nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps)
      updatePayload = []
      break
    default:
      lastProps = lastRawProps
      nextProps = nextRawProps
      if (
        typeof lastProps.onClick !== 'function' &&
        typeof nextProps.onClick === 'function'
      ) {
        // TODO: This cast may not be sound for SVG, MathML or custom elements.
        trapClickOnNonInteractiveElement(((domElement: any): HTMLElement))
      }
      break
  }

  assertValidProps(tag, nextProps)

  let propKey
  let styleName
  let styleUpdates = null
  for (propKey in lastProps) {
    if (
      nextProps.hasOwnProperty(propKey) ||
      !lastProps.hasOwnProperty(propKey) ||
      lastProps[propKey] == null
    ) {
      continue
    }
    if (propKey === STYLE) {
      const lastStyle = lastProps[propKey]
      for (styleName in lastStyle) {
        if (lastStyle.hasOwnProperty(styleName)) {
          if (!styleUpdates) {
            styleUpdates = {}
          }
          styleUpdates[styleName] = ''
        }
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
      // Noop. This is handled by the clear text mechanism.
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // Noop. It doesn't work on updates anyway.
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      // This is a special case. If any listener updates we need to ensure
      // that the "current" fiber pointer gets updated so we need a commit
      // to update this element.
      if (!updatePayload) {
        updatePayload = []
      }
    } else {
      // For all other deleted properties we add it to the queue. We use
      // the whitelist in the commit phase instead.
      ;(updatePayload = updatePayload || []).push(propKey, null)
    }
  }
  for (propKey in nextProps) {
    const nextProp = nextProps[propKey]
    const lastProp = lastProps != null ? lastProps[propKey] : undefined
    if (
      !nextProps.hasOwnProperty(propKey) ||
      nextProp === lastProp ||
      (nextProp == null && lastProp == null)
    ) {
      continue
    }
    if (propKey === STYLE) {
      if (__DEV__) {
        if (nextProp) {
          // Freeze the next style object so that we can assume it won't be
          // mutated. We have already warned for this in the past.
          Object.freeze(nextProp)
        }
      }
      if (lastProp) {
        // Unset styles on `lastProp` but not on `nextProp`.
        for (styleName in lastProp) {
          if (
            lastProp.hasOwnProperty(styleName) &&
            (!nextProp || !nextProp.hasOwnProperty(styleName))
          ) {
            if (!styleUpdates) {
              styleUpdates = {}
            }
            styleUpdates[styleName] = ''
          }
        }
        // Update styles that changed since `lastProp`.
        for (styleName in nextProp) {
          if (
            nextProp.hasOwnProperty(styleName) &&
            lastProp[styleName] !== nextProp[styleName]
          ) {
            if (!styleUpdates) {
              styleUpdates = {}
            }
            styleUpdates[styleName] = nextProp[styleName]
          }
        }
      } else {
        // Relies on `updateStylesByID` not mutating `styleUpdates`.
        if (!styleUpdates) {
          if (!updatePayload) {
            updatePayload = []
          }
          updatePayload.push(propKey, styleUpdates)
        }
        styleUpdates = nextProp
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      const nextHtml = nextProp ? nextProp[HTML] : undefined
      const lastHtml = lastProp ? lastProp[HTML] : undefined
      if (nextHtml != null) {
        if (lastHtml !== nextHtml) {
          ;(updatePayload = updatePayload || []).push(propKey, '' + nextHtml)
        }
      } else {
        // TODO: It might be too late to clear this if we have children
        // inserted already.
      }
    } else if (propKey === CHILDREN) {
      if (
        lastProp !== nextProp &&
        (typeof nextProp === 'string' || typeof nextProp === 'number')
      ) {
        ;(updatePayload = updatePayload || []).push(propKey, '' + nextProp)
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        // We eagerly listen to this even though we haven't committed yet.
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp)
        }
        ensureListeningTo(rootContainerElement, propKey)
      }
      if (!updatePayload && lastProp !== nextProp) {
        // This is a special case. If any listener updates we need to ensure
        // that the "current" props pointer gets updated so we need a commit
        // to update this element.
        updatePayload = []
      }
    } else {
      // For any other property we always add it to the queue and then we
      // filter it out using the whitelist during the commit.
      ;(updatePayload = updatePayload || []).push(propKey, nextProp)
    }
  }
  if (styleUpdates) {
    ;(updatePayload = updatePayload || []).push(STYLE, styleUpdates)
  }
  return updatePayload
}