React中渲染组件与调用返回JSX的函数的差异及行为原因问询
核心原因:内部定义的组件在父组件重渲染时会被React判定为不同类型的组件,触发强制卸载重挂载
你遇到的异常表现本质是React的虚拟DOM Diff规则和组件定义位置的共同作用结果,两种写法的底层差异如下:
1. <ShortAnswer /> 写法的运行逻辑
- 你把
ShortAnswer组件定义在了SomeComponent的函数体内部,每次SomeComponent因为typedAnswer状态更新触发重渲染时,都会重新生成一个全新的ShortAnswer函数引用。 - React做虚拟DOM Diff时,判断两个节点是否可以复用的首要条件是组件的构造函数/函数引用是否相同。只要引用不同,React会直接判定为不同类型的组件,会走完整的旧组件卸载、新组件挂载流程,而不是复用现有组件更新属性。
- 每次输入触发
onChange更新typedAnswer后,旧的ShortAnswer包裹的Input直接被销毁,新的Input被挂载到页面上:- 带
autofocus属性时,新挂载的输入框会自动聚焦,但原有的光标位置信息已经随着旧组件销毁丢失,光标会自动跳到输入框末尾,就会出现你输入中间字符后光标跳转,最终得到te1st2的异常结果; - 不带
autofocus属性时,新挂载的输入框没有焦点,你需要重新点击才能输入下一个字符,就表现为每次仅能输入一个字符。
- 带
2. ShortAnswer() 写法的运行逻辑
- 这种写法本质没有把
ShortAnswer当做独立组件渲染,只是把它当成了一个普通的返回JSX的工具函数,返回的Input元素直接作为SomeComponent渲染结果的一部分,和你直接把Input写在SomeComponent的return里完全等价。 - 此时Diff的主体是
Input元素,它的类型没有变化,也没有卸载重挂载的操作,React只会更新它的value属性,原有的光标、焦点状态都会完整保留,所以输入表现完全正常。
正确修复方案
不要在组件内部定义其他组件,把ShortAnswer移到SomeComponent外部,把依赖的typedAnswer、handleInputChange作为props传入即可,示例如下:
// 移到外部定义,引用永远稳定,不会触发不必要的卸载重挂载 const ShortAnswer = ({ typedAnswer, handleInputChange }) => ( <Input type="text" name="answer" value={typedAnswer} autofocus handleInputChange={handleInputChange} /> ); const SomeComponent = (arg) => { const [typedAnswer, setTypedAnswer] = useState(); const shouldAskForUserInput= arg === 1; const handleInputChange = (e) => setTypedAnswer(e.target.value); return ( <div> <p>whatever</p> <p>something else</p> {shouldAskForUserInput && <ShortAnswer typedAnswer={typedAnswer} handleInputChange={handleInputChange} />} </div> ) }
内容的提问来源于stack exchange,提问作者Miroslav Saracevic
相关产品推荐
相关产品推荐

