为什么 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>;
}
我有一个奇怪的 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>;
}