React

记录一些我见到的react面试题

react合成事件

合成事件(SyntheticEvent),是对浏览器原生事件对象的一层封装,兼容主流浏览器,同时拥有与浏览器原生事件相同的API,如stopPropagationpreventDefault

SyntheticEvent存在的目的是消除不同浏览器在事件对象间的差异,换句话说,抹平了浏览器兼容性差异

react16和react17合成事件和原生事件的执行顺序问题

我们有如下jsx,具体demo

<div
  ref={divDom}
  className="App1"
  onClickCapture={(e) => {
    e.stopPropagation();
    logFunc("div", true, true);
  }}
  onClick={(e) => {
    logFunc("div", true);
  }}
>
  <h1
    ref={h1Dom}
    onClickCapture={() => logFunc("h1", true, true)}
    onClick={(e) => {
      logFunc("h1", true);
    }}
  >
    Hello CodeSandbox
  </h1>
</div>

react16执行顺序

aaa

可以看到,react16的执行顺序是:

  • 原生,捕获,document
  • 原生,捕获,div
  • 原生,捕获,h1
  • 原生,冒泡,h1
  • 原生,冒泡,div
  • 合成,捕获,div
  • 合成,捕获,h1
  • 合成,冒泡,h1
  • 合成,冒泡,div
  • 原生,冒泡,document

流程图总结

bbb

当原生事件执行到document的冒泡阶段时,react在这个阶段开始执行合成事件,当合成事件流执行完成后,会执行document的冒泡阶段

题外话

stopPropagation:阻止捕获和冒泡阶段中当前事件的进一步传播

摘自MDN

Event 接口的 stopPropagation() 方法阻止捕获和冒泡阶段中当前事件的进一步传播。但是,它不能防止任何默认行为的发生;例如,对链接的点击仍会被处理。如果要停止这些行为,请参见 preventDefault() 方法,它可以阻止事件触发后默认动作的发生。它也不能阻止附加到相同元素的相同事件类型的其他事件处理器,如果要阻止这些处理器的运行,请参见 stopImmediatePropagation() 方法。

那么,当我们给div加上stopPropagation之后,就只会执行原生document的捕获事件和原生div的捕获事件

const divClickCapFunc = (e) => {
  e.stopPropagation();  // 这里阻止捕获进行下去
  logFunc("div", false, true);
};

image

当我们给div的合成事件的事件流中断

<div
  ref={divDom}
  className="App1"
  onClickCapture={(e) => {
    e.stopPropagation();  // 这里加上stop
    logFunc("div", true, true);
  }}
  onClick={(e) => {
    logFunc("div", true);
  }}
>
  <h1
    ref={h1Dom}
    onClickCapture={() => logFunc("h1", true, true)}
    onClick={(e) => {
      logFunc("h1", true);
    }}
  >
    Hello CodeSandbox
  </h1>
</div>

image

执行顺序为:

  • 原生,捕获,document
  • 原生,捕获,div
  • 原生,捕获,h1
  • 原生,冒泡,h1
  • 原生,冒泡,div
  • 合成,捕获,div
  • 原生,冒泡,document

原生事件不变,合成事件只执行到了div的捕获

react17执行顺序

把上面的demo改成17就能看到效果了

image.png

执行顺序为:

  • 原生,捕获,document
  • 合成,捕获,div
  • 合成,捕获,h1
  • 原生,捕获,div
  • 原生,捕获,h1
  • 原生,冒泡,h1
  • 原生,冒泡,div
  • 合成,冒泡,h1
  • 合成,冒泡,div
  • 原生,冒泡,document

可以看到,区别就是,合成事件的捕获时机提前了,变成在document的捕获时机之后,原生捕获时机之前

当我们给div加上stopPropagation之后,由于合成事件的捕获时机提前,所以也能够执行合成事件的捕获时机

const divClickCapFunc = (e) => {
  e.stopPropagation();  // 这里阻止捕获进行下去
  logFunc("div", false, true);
};

image.png

当我们给div的合成事件的事件流中断

image.png

就只会执行到合成事件,不会执行原生捕获事件

为什么react17将合成事件挂在根节点

react事件的优先级

通过getEventPriority方法,可以看到每个事件在react中的优先级排序

下面选取部分内容展示优先级排序

DiscreteEventPriority
case 'cancel':
case 'click':
case 'close':
case 'contextmenu':
case 'copy':
case 'cut':
case 'auxclick':
case 'dblclick':
case 'dragend':
case 'dragstart':
case 'drop':
case 'focusin':
case 'focusout':
case 'input':
case 'invalid':
case 'keydown':
case 'keypress':
case 'keyup':
case 'mousedown':
case 'mouseup':
case 'paste':
case 'pause':
case 'play':
case 'pointercancel':
case 'pointerdown':
case 'pointerup':
case 'ratechange':
case 'reset':
case 'resize':
case 'seeked':
case 'submit':
case 'touchcancel':
case 'touchend':
case 'touchstart':
case 'volumechange':
// Used by polyfills:
// eslint-disable-next-line no-fallthrough
case 'change':
case 'selectionchange':
case 'textInput':
case 'compositionstart':
case 'compositionend':
case 'compositionupdate':
// Only enableCreateEventHandleAPI:
// eslint-disable-next-line no-fallthrough
case 'beforeblur':
case 'afterblur':
// Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case 'beforeinput':
case 'blur':
case 'fullscreenchange':
case 'focus':
case 'hashchange':
case 'popstate':
case 'select':
case 'selectstart':
ContinuousEventPriority
case 'drag':
case 'dragenter':
case 'dragexit':
case 'dragleave':
case 'dragover':
case 'mousemove':
case 'mouseout':
case 'mouseover':
case 'pointermove':
case 'pointerout':
case 'pointerover':
case 'scroll':
case 'toggle':
case 'touchmove':
case 'wheel':
// Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case 'mouseenter':
case 'mouseleave':
case 'pointerenter':
case 'pointerleave':
message事件特殊处理
case 'message': {
  // We might be in the Scheduler callback.
  // Eventually this mechanism will be replaced by a check
  // of the current priority on the native scheduler.
  const schedulerPriority = getCurrentSchedulerPriorityLevel();
  switch (schedulerPriority) {
    case ImmediateSchedulerPriority:
      return DiscreteEventPriority;
    case UserBlockingSchedulerPriority:
      return ContinuousEventPriority;
    case NormalSchedulerPriority:
    case LowSchedulerPriority:
      // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
      return DefaultEventPriority;
    case IdleSchedulerPriority:
      return IdleEventPriority;
    default:
      return DefaultEventPriority;
  }
}
其他事件默认是DefaultEventPriority
default:
  return DefaultEventPriority;

useEffect执行顺序问题

面试被问到,没答出来,记录一下

有这样的一份代码,大概图例如下

image.png

代码太长了
component
import "./styles.css";
import { useEffect } from 'react'

function Grand({ children }) {
  useEffect(() => {
    console.log('Grand')
  }, [])

  return (
    <div>{children}</div>
  )
}

function ParentA({ children }) {
  useEffect(() => {
    console.log('ParentA')
  }, [])
  return (
    <div>{children}</div>
  )
}

function ChildA({ children }) {
  useEffect(() => {
    console.log('ChildA')
  }, [])
  return (
    <div>{children}</div>
  )
}

function GrandsonA({ children }) {
  useEffect(() => {
    console.log('GrandsonA')
  }, [])
  return (
    <div>{children}</div>
  )
}

function ChildB({ children }) {
  useEffect(() => {
    console.log('ChildB')
  }, [])
  return (
    <div>{children}</div>
  )
}

function ParentB({ children }) {
  useEffect(() => {
    console.log('ParentB')
  }, [])
  return (
    <div>{children}</div>
  )
}

function ChildC({ children }) {
  useEffect(() => {
    console.log('ChildC')
  }, [])
  return (
    <div>{children}</div>
  )
}

export default function App() {
  return (
    <div className="App">
      <Grand>
        im grand
        <ParentA>
          im parentA
          <ChildA>
            im childA
            <GrandsonA>
              im grandsonA
            </GrandsonA>
          </ChildA>
          <ChildB>
            im childB
          </ChildB>
        </ParentA>
        <ParentB>
          im parentB
          <ChildC>
            im childC
          </ChildC>
        </ParentB>
      </Grand>
    </div>
  );
}
console
GrandsonA 
ChildA 
ChildB 
ParentA 
ChildC 
ParentB 
Grand 

useEffect执行时机

Updated on 4/19/2023