**结论先行: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` 打断点,比猜挂载点快得多。