React组件props.itemId与自定义this.itemId值不一致问题排查
这个问题的核心在于类实例字段的初始化时机,咱们一步步拆解来理解:
为什么props和实例变量会不一致?
你在类顶层声明的itemId = this.props.itemId ? this.props.itemId : "new"属于ES6类实例字段语法,它的初始化时机是组件实例被创建的那一刻(也就是第一次挂载时),而且只会执行这一次。
当你执行列表排序移动操作时,父组件传递的itemId props确实更新了,但React会复用已有的组件实例(不会销毁重建),只是更新它的props。这时候类实例字段不会重新计算,所以this.itemId还是保留了第一次初始化时的旧值,而this.props.itemId已经是新的正确值了。
你之前尝试把赋值逻辑移到constructor或componentDidMount里也没用,因为这两个生命周期函数同样是只在组件挂载时执行一次,props更新时不会再次触发,自然无法同步新的props值。
解决方法
这里有几个简单有效的方案,你可以根据自己的需求选择:
方案1:直接在使用时取最新props(最推荐)
既然itemId只是props.itemId的一个默认值包装,完全没必要把它存在实例变量里,直接在需要的地方实时计算即可:
render() { const itemId = this.props.itemId || "new"; console.log("this.props.itemId = " + this.props.itemId); console.log("itemId = " + itemId); return ( <div id={itemId}>{showValue}</div> ) }
这样每次render都会根据最新的props计算出正确的itemId,从根源避免了不同步的问题。
方案2:用getter方法动态计算
如果你习惯用this.itemId的写法,可以把它改成类的getter属性,每次访问都会重新计算最新值:
class ItemField extends Component { constructor(props) { super(props); this.state = { // temp value of inputs } } // 用getter动态获取最新的itemId get itemId() { return this.props.itemId || "new"; } render() { console.log("this.props.itemId = " + this.props.itemId); console.log("this.itemId = " + this.itemId); return ( <div id={this.itemId}>{showValue}</div> ) } }
方案3:用state同步props(适合需要额外处理的场景)
如果itemId需要和其他状态联动,或者需要在props变化时做一些额外逻辑,可以把它存在state里,然后在props更新时同步:
class ItemField extends Component { constructor(props) { super(props); this.state = { itemId: props.itemId || "new" } } // 当props更新时同步state componentDidUpdate(prevProps) { if (prevProps.itemId !== this.props.itemId) { this.setState({ itemId: this.props.itemId || "new" }); } } render() { console.log("this.props.itemId = " + this.props.itemId); console.log("this.state.itemId = " + this.state.itemId); return ( <div id={this.state.itemId}>{showValue}</div> ) } }
不过这种方式需要额外处理props更新的逻辑,除非有特殊需求,否则方案1或2会更简洁。
内容的提问来源于stack exchange,提问作者Wronski

