Next.js中MobX异步拉取数据后状态更新未触发页面渲染
问题描述
Next.js + TypeScript 项目中集成MobX做状态管理,Store中定义的API调用方法可正常发起请求、获取返回数据,控制台日志可确认Store内部状态已完成更新,但已渲染的页面不会同步刷新,无法展示接口返回的实际数据。
复现代码
Store 实现代码
interface SerializedStore { PageTitle: string; content: string; isOpen: boolean; companiesDto: CompanyDto[], companyCats: string[] }; export class AwardStore { PageTitle: string = 'Client Experience Awards'; companiesDto : CompanyDto[] = []; companyCats: string[] = []; loadingInitial: boolean = true constructor() { makeAutoObservable(this) } hydrate(serializedStore: SerializedStore) { this.PageTitle = serializedStore.PageTitle != null ? serializedStore.PageTitle : "Client Experience Awards"; this.companyCats = serializedStore.companyCats != null ? serializedStore.companyCats : []; this.companiesDto = serializedStore.companiesDto != null ? serializedStore.companiesDto : []; } changeTitle = (newTitle: string) => { this.PageTitle = newTitle; } loadCompanies = async () => { this.setLoadingInitial(true); axios.get<CompanyDto[]>('MyAPICall') .then((response) => { runInAction(() => { this.companiesDto = response.data.sort((a, b) => a.name.localeCompare(b.name)); response.data.map((company : CompanyDto) => { if (company.categories !== null ) { company.categories?.forEach(cat => { this.addNewCateogry(cat) }) } }) console.log(this.companyCats); this.setLoadingInitial(false); }) }) .catch(errors => { this.setLoadingInitial(false); console.log('There was an error getting the data: ' + errors); }) } addNewCateogry = (cat : string) => { this.companyCats.push(cat); } setLoadingInitial = (state: boolean) => { this.loadingInitial = state; } } export async function fetchInitialStoreState() { // 初始Store状态获取逻辑 return {}; }
应用入口 _app.js 实现代码
class MyApp extends App { constructor(props) { super(props); this.state = { awardStore: new AwardStore() }; this.state.awardStore.loadCompanies(); } // 获取序列化的Store初始状态 static async getInitialProps(appContext) { const appProps = await App.getInitialProps(appContext); const initialStoreState = await fetchInitialStoreState(); return { ...appProps, initialStoreState }; } // 将序列化状态注水到Store实例 static getDerivedStateFromProps(props, state) { state.awardStore.hydrate(props.initialStoreState); return state; } render() { const { Component, pageProps } = this.props; return ( <Provider awardStore={this.state.awardStore}> <Component {...pageProps} /> </Provider> ); } } export default MyApp;
故障原因
- 静态渲染配置错误:原代码在模块顶层执行
enableStaticRendering(isServer),Next.js构建阶段会将服务端侧的判断结果固定为true,导致客户端环境下MobX的响应式追踪机制被禁用,状态变更无法通知到订阅的组件。 - 注水逻辑执行时机错误:
getDerivedStateFromProps会在组件每次渲染前执行,每次都会用空的初始Store状态覆盖客户端已经更新完成的Store数据,接口返回的数据刚写入Store就被重置为空值。 - 组件未订阅状态变更:消费Store状态的页面/组件没有包裹MobX的
observer高阶组件,不会监听Store的状态变化事件,状态更新后组件不会触发重渲染。 - 拼写隐患:分类新增方法名拼写错误(
addNewCateogry应为addNewCategory),后续调用容易出现找不到方法的问题。
修复步骤
- 修正静态渲染配置
移除模块顶层的enableStaticRendering调用,仅在服务端渲染环境下开启该配置,保证客户端响应式逻辑正常运行:
if (typeof window === "undefined") { enableStaticRendering(true); }
- 调整注水逻辑执行时机
删除getDerivedStateFromProps中的重复注水逻辑,仅在Store实例首次初始化时执行一次注水,避免后续客户端状态被初始值覆盖:
constructor(props) { super(props); this.state = { awardStore: new AwardStore() }; // 仅首次构造时执行一次注水 if (props.initialStoreState) { this.state.awardStore.hydrate(props.initialStoreState); } this.state.awardStore.loadCompanies(); } // 删除原有的getDerivedStateFromProps静态方法
- 给消费Store状态的组件添加响应式包裹
所有读取、使用Store状态的函数组件用observer(来自mobx-react-lite)包裹,类组件用mobx-react导出的observer包裹,让组件可以订阅Store的状态变更,状态更新时自动重渲染:
import { observer } from "mobx-react-lite"; // 包裹后组件内读取的awardStore状态变更时会自动刷新 const CompanyCategoryList = observer(({ awardStore }) => { return ( <div className="category-list"> {awardStore.companyCats.map(cat => ( <span className="category-tag" key={cat}>{cat}</span> ))} </div> ) })
- 修正方法名拼写错误
将addNewCateogry统一修正为addNewCategory,避免后续调用出现低级错误。
内容的提问来源于stack exchange,提问作者Ivana Murray
相关产品推荐
相关产品推荐

