为何React非受控输入元素在Reconciliation时丢失输入内容?
非受控输入框在切换包裹元素后丢失内容的原因
export default class App extends Component { constructor(props) { super(props); this.state = { counter: 0, wrapContent: false } } handleAdd = () => { this.setState({ counter: this.state.counter + 1 }); } toggleWrap = () => { this.setState(state => state.wrapContent = !state.wrapContent); } wrapContent(content) { return this.state.wrapContent ? <div className="bg-secondary p-2"> <div className="bg-light">{content}</div> </div> : content; } render() { return this.wrapContent( <React.Fragment> <input type="checkbox" checked={this.state.wrapContent} onChange={this.toggleWrap} /> <input className="form-control"/> {/* <--------value disappear after toggling */} <DisplayCounter counter={this.state.counter} /> <div className="text-center"> <button className="btn btn-primary" onClick={this.handleAdd}>Increment</button> </div> </React.Fragment> ); } } class DisplayCounter extends Component { render() { return <React.Fragment><div> <h2>{this.props.counter}</h2> </div></React.Fragment> } }
你遇到的现象是:在输入框中输入文本,点击递增按钮后切换复选框,输入框的值消失,但DisplayCounter的数值仍保留。核心疑惑是:React的Virtual DOM明明能最小化更新,为什么对非受控输入元素无效?
根本原因
这和React的DOM diff算法逻辑直接相关:
- 非受控输入的DOM被销毁重建:你的
wrapContent函数在切换状态时,会把原本直接返回的内容(包含input),改为嵌套在两层<div>中。React在对比前后两次渲染的DOM结构时,会发现input元素的父级层级完全改变——之前是直接在React.Fragment下,现在变成了内层<div>的子元素。此时React会判定这是一个全新的DOM元素,直接销毁原来的input(里面存储着用户输入的内容),重新创建一个默认值为空的新input,导致输入内容丢失。 - 自定义组件不受影响:
DisplayCounter是自定义React组件,React在diff时会优先对比组件类型。只要组件类型一致(都是DisplayCounter),React就会复用已有的组件实例,不会卸载重建。它的数值来自父组件传递的counterprops,始终由父state维护,所以切换包裹结构时,组件实例保留,props正常更新,数值自然不会丢失。
补充说明
非受控输入的内容只存储在DOM节点本身,没有和React的state绑定,一旦DOM节点被销毁,里面的内容就会丢失。而受控输入的内容由state维护,即使DOM重建,也会从state中读取值渲染,不会丢失。另外,也可以给input添加一个固定的key属性,让React识别这是同一个元素,即使父级结构变化,也会复用该DOM节点,从而保留输入内容。
内容的提问来源于stack exchange,提问作者lch
相关产品推荐
相关产品推荐

