React点击事件触发重渲染时WaypointList组件未更新问题排查
问题原因
1. React Diff算法的组件复用逻辑
你两次渲染的都是WaypointList组件,React的Diff算法会判定为同一个组件实例,只会更新传入的props,不会销毁旧实例重新创建新实例,因此WaypointList的constructor只会在首次挂载时执行一次。而你换成<div>see</div>时,前后节点类型不同,React会直接销毁旧的WaypointList渲染新的div,所以能看到变化。
2. WaypointList未响应props更新
你把WaypointList的渲染内容content存在了组件state中,且只在constructor里执行了一次index(props)生成content,后续父组件传入新的waypoint_is_done prop时,没有对应的逻辑更新state里的content,因此视图不会发生变化。
3. 错误的state使用方式
App组件中将JSX组件直接存入state是React的反模式,state应该存储原始状态值,而非已经渲染好的JSX节点,这种写法会增加不必要的diff复杂度,也容易出现你遇到的这类更新失效问题。
修复方案
第一步:修改App组件,状态存储原始值
class App extends React.Component { constructor(props) { super(props); // 只存储状态标识,不存JSX this.state = { waypoint_is_done: false }; this.toggleDoneList = this.toggleDoneList.bind(this); }; toggleDoneList(){ this.setState({ waypoint_is_done: true }); }; render() { // 渲染时动态生成组件 return <WaypointList waypoint_is_done={this.state.waypoint_is_done} App={this}/>; }; } ReactDOM.render( <App/>, document.getElementById('app') );
第二步:修改WaypointList,响应props变化
优先选择移除不必要的state,直接在render方法中根据props计算渲染内容:
class WaypointList extends React.Component { constructor(props) { super(props); this.App = props.App; }; render() { const rows = logistic_route_sheet_waypoint_rows; if (rows.length <= 0) return null; return ( <div className="map-listing"> <div className="object-done-listing noselect btn btn-success" onClick={() => this.App.toggleDoneList()}> <i className="fa fa-list" aria-hidden="true"></i> </div> {/* 可根据props.waypoint_is_done渲染不同内容 */} {this.props.waypoint_is_done ? <p>已完成路点列表</p> : <p>未完成路点列表</p>} </div> ); }; }
如果确实需要将渲染结果存在state中,需要添加componentDidUpdate生命周期监听props变化:
class WaypointList extends React.Component { constructor(props) { super(props); this.App = props.App; this.state = { content: this.index(props) }; this.index = this.index.bind(this); }; // 监听props变化,更新state componentDidUpdate(prevProps) { if(prevProps.waypoint_is_done !== this.props.waypoint_is_done) { this.setState({ content: this.index(this.props) }) } } index(props) { let rows = logistic_route_sheet_waypoint_rows; if (rows.length > 0) { return ( <div className="map-listing"> <div className="object-done-listing noselect btn btn-success" onClick={() => this.App.toggleDoneList()}> <i className="fa fa-list" aria-hidden="true"></i> </div> {props.waypoint_is_done ? <p>已完成路点列表</p> : <p>未完成路点列表</p>} </div> ); } return null; }; render() { return this.state.content; }; }
内容的提问来源于stack exchange,提问作者user2573099
相关产品推荐
相关产品推荐

