Apollo Client+React:Mutation更新缓存后UI未同步问题
看起来你的核心问题在于BookingsBlock组件的状态没有随着props更新而同步,导致后续子组件无法获取到最新的预约数据。咱们一步步拆解问题并解决:
为什么UI没更新?
你的BookingsBlock是class组件,区分历史/未来预约的逻辑写在了constructor里——但constructor只会在组件初始化时执行一次。当Apollo缓存更新后,Query组件会重新获取数据并传递新的appointments props给BookingsBlock,但此时组件已经初始化完成,不会再重新执行constructor,所以this.state.pastAppointments和futureAppointments始终是旧数据,自然子组件的UI也不会同步更新。
简单说:你把处理后的数据存在了state里,但没有监听props的变化来更新state,这是class组件中常见的误区。
解决方案
方案1:移除state,直接在render中计算(推荐)
既然区分历史/未来的逻辑只是对props的简单处理,完全没必要存在state里,直接在render的时候计算即可。这样每次props更新,render都会重新计算,自动同步最新数据:
export default class BookingsBlock extends Component { render() { const { appointments } = this.props; let pastAppointments = []; let futureAppointments = []; if (appointments) { appointments.forEach(appt => { if (moment(appt.appointmentStart) < moment()) { pastAppointments.push(appt); } else { futureAppointments.push(appt); } }); } // 后续直接用这两个变量渲染即可 return ( // ...你的渲染内容,比如分别渲染历史/未来预约列表 ); } }
方案2:用componentDidUpdate监听props变化更新state
如果你确实需要把处理后的数据存在state里,那就要在props变化时手动更新state:
export default class BookingsBlock extends Component { constructor(props) { super(props); this.state = { pastAppointments: [], futureAppointments: [] }; this.processAppointments = this.processAppointments.bind(this); } componentDidMount() { // 初始化时处理一次数据 this.processAppointments(this.props.appointments); } componentDidUpdate(prevProps) { // 只有当appointments props变化时才更新state if (prevProps.appointments !== this.props.appointments) { this.processAppointments(this.props.appointments); } } processAppointments(appointments) { let pastAppointments = []; let futureAppointments = []; if (appointments) { appointments.forEach(appt => { if (moment(appt.appointmentStart) < moment()) { pastAppointments.push(appt); } else { futureAppointments.push(appt); } }); } this.setState({ pastAppointments, futureAppointments }); } // ...你的render逻辑 }
方案3:用getDerivedStateFromProps(React官方推荐的class组件状态更新方式)
这是React为依赖props更新state提供的静态方法,更符合现代class组件的规范:
export default class BookingsBlock extends Component { static getDerivedStateFromProps(nextProps, prevState) { // 当appointments变化时,重新计算状态 if (nextProps.appointments !== prevState.lastAppointments) { let pastAppointments = []; let futureAppointments = []; if (nextProps.appointments) { nextProps.appointments.forEach(appt => { if (moment(appt.appointmentStart) < moment()) { pastAppointments.push(appt); } else { futureAppointments.push(appt); } }); } return { pastAppointments, futureAppointments, lastAppointments: nextProps.appointments // 记录当前的appointments,用于下次对比 }; } return null; // 不需要更新状态时返回null } constructor(props) { super(props); this.state = { pastAppointments: [], futureAppointments: [], lastAppointments: props.appointments }; } // ...你的render逻辑 }
额外检查点
虽然你说已经统一了Query和Mutation的返回字段,但还是可以确认下:
- Mutation返回的
id和Query返回的id完全一致(类型、值都要匹配),Apollo是通过id(或者你配置的dataIdFromObject)来识别缓存中的对象的; - 确认Mutation的
update函数(如果有的话)没有错误——不过你说Apollo开发者工具显示缓存已经更新,这一步可能没问题,但还是可以检查下。
按照上面的方案修改后,当Mutation更新缓存,Query组件重新渲染传递新的appointments给BookingsBlock,组件会重新处理数据,子组件Appointment和CancelAppointment就能拿到最新的status,UI也就同步更新了。
内容的提问来源于stack exchange,提问作者BML91

