React × Vue

useRef 与模板 ref

React 通过 useRef 和 ref prop 暴露 DOM;Vue 使用模板 ref 与 defineExpose 访问子组件方法。

核心结论

Ref 是逃生口:它保存一个不会直接参与声明式渲染的可变引用,常用于聚焦、测量或对接第三方命令式 API。对子组件暴露能力时,应只公开必要方法。

命令式通道

组件关系与数据流

ComponentRef / DOM

差异焦点函数组件不会自动暴露实例,需要 forwardRef 和 useImperativeHandle。

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

flowchart TB
  accTitle: React forwardRef 命令式句柄
  accDescr: SearchInput 必须通过 forwardRef 接收 ref,再用 useImperativeHandle 构造受控句柄;ReactDemo 调用句柄时最终操作 input DOM。
  parent["ReactDemo"] -->|"ref prop"| child["SearchInput + forwardRef"]
  child --> expose{{"useImperativeHandle 构造句柄"}}
  expose --> handle[("SearchInputHandle")]
  parent -.->|"focus() / select()"| handle
  handle --> dom[("input DOM")]
  class parent,child component
  class expose process
  class handle,dom 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

forwardRef 接收 ref,useImperativeHandle 显式构造父组件可调用的句柄。

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

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

组件关系与数据流

ComponentRef / DOM

差异焦点模板 ref 直接获得组件公开实例,defineExpose 决定暴露边界。

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

flowchart TB
  accTitle: Vue template ref 与 defineExpose
  accDescr: VueDemo 使用模板 ref 获取子组件公开实例,SearchInput 通过 defineExpose 限定公开方法,方法内部再操作 input 模板 ref。
  child["SearchInput"] --> expose{{"defineExpose 限定公开方法"}}
  expose --> instance[("组件公开实例")]
  parent["VueDemo"] -.->|"template ref"| instance
  instance -->|"focus() / select()"| inputRef[("input template ref")]
  inputRef --> dom[("input DOM")]
  class parent,child component
  class expose process
  class instance,inputRef,dom 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

VueDemo → template ref → defineExpose → SearchInput → input DOM

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

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

完整 Demo 源码

以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。

React 源码

ReactDemo.tsx
import { forwardRef, useImperativeHandle, useRef, useState } from "react";type SearchInputHandle = {  focus: () => void;  select: () => void;};const SearchInput = forwardRef<SearchInputHandle>(function SearchInput(_, ref) {  const inputRef = useRef<HTMLInputElement>(null);  useImperativeHandle(ref, () => ({    focus: () => inputRef.current?.focus(),    select: () => inputRef.current?.select(),  }), []);  return (    <label className="demo-field">      子组件输入框      <input        ref={inputRef}        className="demo-input"        defaultValue="React forwardRef"      />    </label>  );});export function ReactDemo() {  const searchRef = useRef<SearchInputHandle>(null);  const [action, setAction] = useState("等待操作");  return (    <div className="demo-stack">      <SearchInput ref={searchRef} />      <div className="demo-actions">        <button          className="demo-button"          type="button"          onClick={() => {            searchRef.current?.focus();            setAction("已聚焦输入框");          }}        >          聚焦        </button>        <button          className="demo-button"          type="button"          onClick={() => {            searchRef.current?.select();            setAction("已全选输入内容");          }}        >          全选        </button>      </div>      <p className="demo-result" aria-live="polite">{action}</p>    </div>  );}

Vue 3 源码

<script setup lang="ts">import { ref } from "vue";import SearchInput from "./SearchInput.vue";const searchRef = ref<InstanceType<typeof SearchInput> | null>(null);const action = ref("等待操作");function focusInput() {  searchRef.value?.focus();  action.value = "已聚焦输入框";}function selectInput() {  searchRef.value?.select();  action.value = "已全选输入内容";}</script><template>  <div class="demo-stack">    <SearchInput ref="searchRef" />    <div class="demo-actions">      <button class="demo-button" type="button" @click="focusInput">        聚焦      </button>      <button class="demo-button" type="button" @click="selectInput">        全选      </button>    </div>    <p class="demo-result" aria-live="polite">{{ action }}</p>  </div></template>

在线观察

父组件调用子组件公开的“聚焦”和“全选”方法,最终操作真实 DOM。

React

等待操作

Vue 3

等待操作

使用边界

  • 能通过 props 描述的 UI 状态,不要改成 ref 命令。
  • 不要在 render 期间读写 DOM ref;DOM 只在提交或挂载后存在。
  • 公开 focus() 比公开整个内部 DOM 更稳定,也更容易重构。
  • ref 中保存的普通值发生变化时不会自动触发 React render;Vue template ref 本身是响应式 ref,但 DOM 操作仍是命令式的。

声明式父子通信见组件通信。