为什么 mobX 动作不会触发 React 组件的重新渲染

Why mobX action doesn't trigger rerendering of React component

我有一个奇怪的 MobX 行为。

这是我在文件 store.tsx 中的商店。您也可以找到它作为 sandbox.

import { action, makeObservable, observable} from "mobx";

export default class ViewStore {
  currentProjectId: string | undefined = undefined;

  constructor() {
    makeObservable(this, {
      currentProjectId: observable,
      setCurrentProjectId: action,
    });
  }

  setCurrentProjectId = (projectId: string | undefined) => {
    this.currentProjectId = projectId;
  };
}

这是我的组件

import React from "react";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";

import ViewStore from "./store";

interface Props {
  localStore: ViewStore;
}

interface UrlParams {
  projectId: string;
}

function AppManageProject({ localStore }: Props) {
  const urlParams = useParams<UrlParams>();

  if (localStore.currentProjectId !== urlParams.projectId) {
    localStore.setCurrentProjectId(urlParams.projectId);
    return <></>;
  }

  return (
        <div>{localStore.currentProjectId}</div>
  );
}

export default observer(AppManageProject);

该页面是一个更大的应用程序的一部分,它是从路线 /<project id> 到达的。当您第一次浏览到例如/prj1,执行 if 块,因此可观察对象 currentProjectId 通过 MobX 操作 setCurrentProjectId 设置为“prj1”。我希望可观察对象的更改会触发重新渲染,现在 urlParams.projectId === localStore.currentProjectId。但是组件实际上并没有重新渲染。

sandbox 中,从控制台的打印输出中,您可以看到您确实输入了 if 块,但未触发重新渲染。

请注意,如果我没有直接调用操作 setCurrentProjectId,而是使用回调,例如setTimeout(() => localStore.setCurrentProjectId(urlParams.projectId), 0) 然后一切正常。

认为回调有效是因为它使用了箭头函数,我也尝试替换

localStore.setCurrentProjectId(urlParams.projectId)

调用箭头函数:

( () => localStore.setCurrentProjectId(urlParams.projectId) ) ()

但这也行不通。

有人知道 explanation/solution 发生了什么事吗? 谢谢

出于某种原因,如果您在渲染函数内部更新某些可观察对象,MobX 不会重新渲染组件。我不知道为什么,但是渲染函数中的副作用无论如何都是一个糟糕的模式,所以它不起作用是有道理的。

为了使您的代码更加地道,您可以使用 useEffect 钩子来处理副作用,例如:

function AppManageProject({ localStore }: Props) {
  const urlParams = useParams<UrlParams>();

  useEffect(() => {
    localStore.setCurrentProjectId(urlParams.projectId);
  }, [urlParams.projectId]);

  if (localStore.currentProjectId !== urlParams.projectId) {
    return <></>;
  }

  return <div>{localStore.currentProjectId}</div>;
}