Pressidian
花园入口
笔记
项目
关于
实验室
GitHub
花园入口
笔记
项目
关于
实验室
GitHub

KNOWLEDGE PATHS

笔记库
当前位置
笔记库/前端/项目笔记/代达罗斯/组件

Lable

2 分钟阅读 · Note

目录树 578 篇

            • 不使用解构赋值
            • Badge[bædʒ]
            • Button
            • CrateCodeSnippetDialog
            • CrateCodeSnippetsPanel
            • Dialog
            • Lable
            • LoadingState
            • ProjectFileTree
            • ProjectFileTree题目
          • 项目待做
          • 性能优化
          • UI设计
      • 前端技术栈
    • 笔记目录
    • CLAUDE.md
    • Vue 组件与 Render 函数

关联笔记 6

↗不使用解构赋值同一路径↗Badge[bædʒ]同一路径↗Button同一路径↗CrateCodeSnippetDialog同一路径↗CrateCodeSnippetsPanel同一路径↗Dialog同一路径
  • Lable

Lable

> Last Format Time:7/14/2026 20:56:57


分析

这个组件里的 ref,是 React 中一个非常经典的用法——forwardRef 转发 ref。

我们先看完整代码:

export const Label = forwardRef<
  HTMLLabelElement,
  LabelHTMLAttributes<HTMLLabelElement>
>(
  ({ className, ...props }, ref) => (
    <label
      ref={ref}
      className={cn("text-sm font-medium", className)}
      {...props}
    />
  ),
);

我们重点只看 ref。


一、为什么普通组件不能接收 ref?

假设你先这样写一个组件:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

别人使用的时候:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

React 会直接报错(或者提示):

> Function components cannot be given refs.

因为:

ref 默认只能绑定到 DOM 元素或者 class 组件。

例如:

<label ref={labelRef}></label>

这里没问题。

但是

<Label ref={labelRef} />

React 根本不知道:

> 这个 ref 应该给 Label 自己?
> 还是 Label 里面的 &lt;label&gt;?

所以函数组件默认是不接收 ref 的。


二、forwardRef 就是解决这个问题

forwardRef 的作用就是:

> 允许父组件把 ref 传进来,然后你决定把它放到哪里。

可以理解成:

父组件
   │
   │ ref
   ▼
Label组件
   │
   │ forwardRef
   ▼
<label>

也就是说:

父组件
↓

const ref = useRef()

↓

<Label ref={ref}/>

↓

forwardRef 接收到 ref

↓

<label ref={ref}/>

最终:

ref.current

指向的是:

<label>

这个 DOM。


三、这里的 ref 参数是哪来的?

很多人第一次都会疑惑:

({ className, ...props }, ref)

为什么第二个参数就是 ref?

因为:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

React 会自动调用你的函数:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

所以:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

和普通组件:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

不同。


四、这个 ref 最终有什么用?

例如:

父组件:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

打印出来:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

也就是说:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

就是:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

DOM 节点。

于是你可以:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

或者:

function Label(props: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label {...props} />;
}

或者:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

等等。

虽然 &lt;label&gt; 本身一般不会 focus(),但对于 &lt;input&gt;、&lt;textarea&gt; 等组件,这种模式非常常见。


五、forwardRef 的泛型是什么意思?

这里有两个泛型:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

第一个:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

表示:

> ref 指向什么。

也就是说:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

类型就是:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

所以才有:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

这些属性都有提示。

第二个:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

表示:

> props 的类型。

也就是说:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

这些都是合法的。

如果写:

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

TS 就会报错。


六、为什么这里又写了 &lt;label ref={ref}&gt;

这一句就是 真正完成转发。

const labelRef = useRef<HTMLLabelElement>(null);

<Label ref={labelRef} />

如果你删掉:

<label ref={labelRef}></label>

变成:

<label ref={labelRef}></label>

那么:

<label ref={labelRef}></label>

虽然不会报错(因为用了 forwardRef),

但是:

<label ref={labelRef}></label>

永远都是:

<label ref={labelRef}></label>

因为你根本没有把它交给任何 DOM。


七、整个流程图

<label ref={labelRef}></label>

所以:

<label ref={labelRef}></label>

实际上就是:

<label ref={labelRef}></label>

八、为什么很多组件库都这样写?

像 shadcn/ui、Radix UI 等组件库,大量组件都会采用这种模式:

<label ref={labelRef}></label>

原因是:让封装后的组件在使用体验上尽可能和原生元素一致。

例如:

<label ref={labelRef}></label>

虽然 Input 是你封装的组件,但父组件仍然可以直接拿到内部的 &lt;input&gt; DOM,进行聚焦、测量、滚动定位等操作。这也是很多可复用组件推荐使用 forwardRef 的原因。


具体实现

<Label ref={labelRef} />