React中Bar()与<Bar/>组件调用方式的差异及问题解析
核心结论
你的原有认知是错误的:<Bar /> 和 Bar() 两种写法效果完全不等价,你遇到的输入首字符后失焦,是React组件卸载重挂载的典型表现。
两种写法的本质差异
Bar()是普通JavaScript函数调用:执行函数后拿到返回的React元素,直接嵌入当前Foo组件的渲染树,属于Foo组件自身渲染逻辑的一部分。React不会为这部分逻辑分配独立的组件节点,没有独立的状态、hooks作用域、生命周期,这部分内容的重渲染完全跟随Foo组件执行,不存在独立的组件实例概念,效果和你直接把<input />写在Foo对应位置完全一致。<Bar />是JSX组件声明:相当于告诉React「当前位置需要渲染一个类型为Bar的独立组件」。React会在虚拟DOM树中为其创建独立的组件节点,分配独立的组件实例、独立的状态存储空间、独立的hooks链表、独立的effect与生命周期作用域,是作为Foo的子级独立组件单元存在的。
输入框失焦的根本原因
问题的触发前提是你把Bar组件定义在了Foo组件的函数内部:
每次Foo组件因为状态变化重渲染时,都会重新创建一个全新的Bar函数引用。React做虚拟DOM Diff时,判断组件是否可复用的第一优先级规则是组件类型的引用是否严格相等。
上一次渲染时,对应位置的组件类型是旧渲染周期创建的
oldBar函数;本次渲染时,对应位置的组件类型是新渲染周期创建的newBar函数,两个引用完全不相等。React会直接判定这是两个完全不同的组件,执行流程是:卸载旧Bar组件(连带销毁内部的input DOM节点)→ 挂载全新的Bar组件(渲染全新的input DOM节点)。每次输入触发Foo重渲染都会走一遍这个流程,input节点每次都被销毁重建,自然会丢失焦点。
直接调用Bar()不会触发这个问题的原因也很简单:函数调用不会产生独立的组件节点,React拿到的直接是<input />元素,原生标签的类型是固定字符串'input',Diff时会判定为可复用节点,只会更新节点属性,不会销毁重建DOM,焦点自然不会丢失。
最佳实践
- 永远不要在一个组件的渲染函数内部定义另一个组件,将子组件挪到父组件外层定义,保证组件引用全局稳定,此时使用
<Bar />写法不会出现任何异常:
// 组件定义挪到外层,引用永久稳定 const Bar = () => { return <input placeholder="some input" /> } export default function Foo(){ return( <div> <div>some text</div> <Bar /> </div> ) }
- 如果子组件确实需要访问父组件作用域内的变量,优先通过props传值的方式实现,不要通过在父组件内定义子组件的方式获取闭包变量。
内容的提问来源于stack exchange,提问作者CODEforDREAM
相关产品推荐
相关产品推荐

