Mobx异常问题:移除console.log后组件不更新,是否依赖该语句?
console.log(userStore.getLoading)后Mobx不再更新PrivateRoute组件? 这问题其实是Mobx依赖追踪机制的典型坑,我来给你拆解清楚:
先看核心问题:Mobx的依赖追踪是怎么工作的?
Mobx的@observer组件能自动更新的前提是:在组件的render函数执行期间,直接访问了被Mobx观察的可观察属性(@observable)或计算属性(@computed)。只有这样,Mobx才会把当前组件加入到这些属性的依赖列表中,当属性值变化时,才会通知组件重新渲染。
你的代码里到底出了什么问题?
看你的PrivateRoute组件:
@observer(['userStore']) class PrivateRoute extends Component { render() { const { component: Component, userStore } = this.props // 移除这句后组件就不更新了 // console.log(userStore.getLoading) return ( <Route render={props => userStore.getLoading ? ( <div>getting auth...</div> ) : userStore.getUser ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: '/login', state: { from: props.location } }} /> )} /> ) } }
你访问userStore.getLoading和userStore.getUser的地方,是在Route的render回调函数里——这个回调并不是在PrivateRoute的render执行时立刻运行的,而是等React Router后续触发路由渲染时才会调用。
Mobx的依赖收集是在PrivateRoute的render函数执行过程中完成的,当render执行完,Mobx就已经结束了依赖追踪。这时候后续回调里的属性访问,Mobx根本“看不到”,自然不会把PrivateRoute绑定到这些属性的依赖列表里。
而当你加上console.log(userStore.getLoading)时,这句代码是在PrivateRoute的render顶层执行的,Mobx在收集依赖时捕捉到了对getLoading的访问,于是把PrivateRoute加入到getLoading的依赖列表中。当getLoading变化时,Mobx就会触发PrivateRoute重新渲染,这时候Route的回调才会再次执行,拿到最新的属性值。
怎么解决?
有两种简单的方式:
方案1:提前在render顶层访问需要追踪的属性
把对getLoading和getUser的访问提到PrivateRoute的render函数顶层,让Mobx在依赖收集阶段就能捕捉到:
@observer(['userStore']) class PrivateRoute extends Component { render() { const { component: Component, userStore } = this.props // 提前访问,让Mobx追踪到这些依赖 const isLoading = userStore.getLoading const hasUser = userStore.getUser return ( <Route render={props => isLoading ? ( <div>getting auth...</div> ) : hasUser ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: '/login', state: { from: props.location } }} /> )} /> ) } }
方案2:把路由回调逻辑抽成独立的Observer组件
把原本在Route回调里的逻辑拆成一个单独的组件,并用@observer包裹,这样Mobx会单独追踪这个子组件的依赖:
// 单独的观察者组件,负责路由内容渲染 const PrivateRouteContent = observer(({ component: Component, userStore, ...props }) => { if (userStore.getLoading) { return <div>getting auth...</div> } if (userStore.getUser) { return <Component {...props} /> } return <Redirect to={{ pathname: '/login', state: { from: props.location } }} /> }) @observer(['userStore']) class PrivateRoute extends Component { render() { const { component: Component, userStore } = this.props return ( <Route render={props => <PrivateRouteContent component={Component} userStore={userStore} {...props} />} /> ) } }
总结一下
Mobx的更新不是“自动感知所有属性变化”,而是只追踪组件render执行期间直接访问的观察属性。把属性访问放在延迟执行的回调里,Mobx就无法追踪到依赖,自然不会触发组件更新。提前访问属性或者拆分组件,就能让Mobx正确追踪依赖,实现自动更新。
内容的提问来源于stack exchange,提问作者dangeneffy

