**结论先行:React 的 e.stopPropagation() 只在"根容器"这一层截断原生冒泡,它拦不住已经跑完的内层原生监听器,也管不了同一个元素上的兄弟监听器。**

展开说几个边界:

1. 它确实能拦住 document 级监听。 React 17+ 把委托监听挂在 root 容器,stopPropagation() 内部会调 nativeEvent.stopPropagation(),让原生事件从 root 再往上就不再冒泡——所以你在 document 上绑的原生 click 不会再触发。这也解释了为什么 demo 里 document 那行会消失。

2. 但 target→root 之间的原生监听器拦不住。 这些监听器在事件到达 root 之前就已经执行完了,React 那时还没轮到它插手。所以如果按钮上直接 addEventListener 绑了一个,它会先跑,stopPropagation 对它无能为力。

3. 同一元素上的兄弟监听器也拦不住。 stopPropagation 只阻止跨元素冒泡,同一元素上其它监听器照跑,要拦得用 e.nativeEvent.stopImmediatePropagation()。

4. 最容易踩的是 portal。 React 事件按 Fiber 树冒泡,不是 DOM 树——portal 出去的节点 DOM 挂在 body 下,但 onClick 照样冒到 React 父组件,stopPropagation 也是按 React 树来算的。这点和你在 DOM 里看到的层级完全对不上。

建议: 真要控制 document 级行为,优先在原生侧用捕获阶段(addEventListener(..., true))处理,别在 React 回调里指望 stopPropagation 搞定一切;调试顺序时直接在 e.nativeEvent 打断点,比猜挂载点快得多。