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.4pxforwardRef 接收 ref,useImperativeHandle 显式构造父组件可调用的句柄。
组件关系与数据流
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.4pxVueDemo → template ref → defineExpose → SearchInput → input DOM
完整 Demo 源码
以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。
React 源码
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 操作仍是命令式的。
声明式父子通信见组件通信。