React × Vue

Custom Hook 与 Composable

React Hook 在组件 render 中按规则调用;Vue composable 在 setup 中组合并返回响应式状态。

核心结论

Custom Hook 和 Composable 都复用有状态逻辑,而不是共享一份组件状态。每个调用者都会获得自己的状态与生命周期连接;差异来自 Hook 参与每次 render,而 Composable 通常在 setup 中调用一次。

复用边界

组件关系与数据流

Reusable StateConsumerHook / ComposableBrowser API

差异焦点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.4px

Hook 随组件 render 调用,通过 Hook 顺序关联保存的 state 和 effect。

React 流程图
React 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

组件关系与数据流

Reusable StateConsumerHook / ComposableBrowser API

差异焦点普通组合函数可以使用控制流组织逻辑,依赖通过响应式引用连接。

实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。

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.4px

Composable 在 setup 中创建并返回 refs,生命周期钩子跟随调用它的组件。

Vue 3 流程图
Vue 3 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

完整 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

useViewportSize()

窄屏

窗口宽度:等待客户端挂载

窗口高度:等待客户端挂载

调整浏览器窗口后,Hook 内部的 resize 监听会更新组件 state。

Vue 3

useViewportSize()

窄屏

窗口宽度:等待客户端挂载

窗口高度:等待客户端挂载

调整浏览器窗口后,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。