Custom Hook 与 Composable
React Hook 在组件 render 中按规则调用;Vue composable 在 setup 中组合并返回响应式状态。
核心结论
Custom Hook 和 Composable 都复用有状态逻辑,而不是共享一份组件状态。每个调用者都会获得自己的状态与生命周期连接;差异来自 Hook 参与每次 render,而 Composable 通常在 setup 中调用一次。
复用边界
组件关系与数据流
差异焦点Custom Hook 必须遵守 Hooks 调用规则,它不是独立运行的组件实例。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: React Custom Hook 运行机制
accDescr: ReactDemo 每次 render 都按固定顺序调用 useViewportSize,Hook 的 effect 管理窗口监听,resize 时 setter 更新 Hook state 并重新执行组件。
demo["ReactDemo render"] --> hookLogic{{"调用 Custom Hook"}}
hookLogic --> effect{{"useEffect 注册与清理"}}
effect --> windowResource[("window resize")]
windowResource -.->|"setSize"| state(["size state"])
state -->|"调度下一次 render"| demo
class state state
class demo component
class hookLogic,effect process
class windowResource resource
classDef state fill:#fffdf8,stroke:#149eca,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f5fa,stroke:#149eca,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f4fafb,stroke:#149eca,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4pxHook 随组件 render 调用,通过 Hook 顺序关联保存的 state 和 effect。
组件关系与数据流
差异焦点普通组合函数可以使用控制流组织逻辑,依赖通过响应式引用连接。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: Vue Composable 运行机制
accDescr: VueDemo setup 调用 useViewportSize 一次,Composable 创建 refs 并登记挂载与卸载回调,resize 时直接修改 refs。
demo["VueDemo setup"] --> composable{{"调用 Composable 一次"}}
composable --> lifecycle{{"登记 onMounted / onUnmounted"}}
lifecycle --> windowResource[("window resize")]
windowResource -.->|"直接修改 refs"| state(["width / height refs"])
state -->|"响应式更新"| template["VueDemo template"]
class state state
class demo,template component
class composable,lifecycle process
class windowResource resource
classDef state fill:#fffdf8,stroke:#2f9d6b,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f4eb,stroke:#2f9d6b,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f2f8f4,stroke:#2f9d6b,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4pxComposable 在 setup 中创建并返回 refs,生命周期钩子跟随调用它的组件。
完整 Demo 源码
以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。
React 源码
import { useViewportSize } from "./useViewportSize";export function ReactDemo() { const { height, label, width } = useViewportSize(); return ( <div className="demo-stack"> <div className="demo-card"> <span className="demo-tag">useViewportSize()</span> <h2>{label}</h2> <p>窗口宽度:{width || "等待客户端挂载"}</p> <p>窗口高度:{height || "等待客户端挂载"}</p> </div> <p className="demo-muted"> 调整浏览器窗口后,Hook 内部的 resize 监听会更新组件 state。 </p> </div> );}Vue 3 源码
<script setup lang="ts">import { useViewportSize as viewportSizeComposable } from "./useViewportSize.vue";const { height, label, width } = viewportSizeComposable();</script><template> <div class="demo-stack"> <div class="demo-card"> <span class="demo-tag">useViewportSize()</span> <h2>{{ label }}</h2> <p>窗口宽度:{{ width || "等待客户端挂载" }}</p> <p>窗口高度:{{ height || "等待客户端挂载" }}</p> </div> <p class="demo-muted"> 调整浏览器窗口后,Composable 内部的 resize 监听会修改 refs。 </p> </div></template>在线观察
调整浏览器宽度,两个 Demo 都会更新当前断点。
React
窄屏
窗口宽度:等待客户端挂载
窗口高度:等待客户端挂载
调整浏览器窗口后,Hook 内部的 resize 监听会更新组件 state。
Vue 3
窄屏
窗口宽度:等待客户端挂载
窗口高度:等待客户端挂载
调整浏览器窗口后,Composable 内部的 resize 监听会修改 refs。
设计规则
- React Hook 必须在组件或其他 Hook 顶层按稳定顺序调用。
- Vue Composable 也应在
setup的同步阶段调用需要组件实例的生命周期 API。 - 复用函数返回最小必要状态和操作,不要泄漏内部订阅细节。
- 多个组件需要共享同一实例时,应把状态提升到 Context/provide 或外部 store,而不是仅仅复用函数。
本站同时运行 React Fast Refresh 与 Vue HMR,因此 Vue Demo 使用中性别名调用 composable;普通 Vue 项目可以直接使用 useViewportSize()。
跨层共享依赖见Context 与 provide/inject。