【问题标题】:Page not rerendering after history.push()history.push() 后页面不重新渲染
【发布时间】:2021-10-25 00:34:25
【问题描述】:

我在我的 React 字典项目中使用 React Router,由于某种原因,在我运行 history.push()(使用 react-router 中的 useHistory 挂钩)后,我的页面没有重新呈现。我有一个搜索栏,我使用此功能转到一个新链接。

const KeyPressHandler: KeyboardEventHandler<HTMLInputElement> = (event) => {
    const { value } = event.currentTarget;
    if ((event.code === "Enter" || event.code === "NumpadEnter") && value.length)
      history.push(`/dictionary/${value}`);
    };
  };

我的App 组件如下所示:

const App = (): JSX.Element => {
  ...
  return (
    ...
      <Route path="/dictionary" component={DictionaryEntryPage} />
    ...
  );
};

这就是DictionaryEntryPage 组件:

const DictionaryEntryPage = (): JSX.Element => {
  const [wordData, setWordData] = useState<WordData[] | undefined | null>(undefined);
  // useRouteMatch is imported from react-router
  const match = useRouteMatch<{ requestedWord: string }>("/dictionary/:requestedWord");

  useEffect(() => {
    const { requestedWord } = match?.params ?? {};

    if (requestedWord) {
      (async () => {
        const data = await parseWordData(requestedWord);
        setWordData(data || null);
      })();
    } else setWordData(null);
  }, []);
  
  const wordDataEls = wordData ? wordData.map((data, i) => <Word {...data} key={i} />) : <Loading />;

  ...
}

让我知道我应该在问题中添加/删除什么,如果有人想看的话,here 是一个演示链接。

【问题讨论】:

  • 你能发布你的应用组件的完整代码吗?
  • 或者你的仓库的链接,如果它在 GitHub 上的话。我怀疑您在 DictionaryEntryPage 中的 useEffect 中缺少依赖项。我相信您可能需要将match 作为依赖项,以在比赛发生变化时强制该效果重新运行。但我真的很想看看 repo 以确定。如果您尝试这样做并解决了它,我很乐意写一个答案来解释原因
  • 记录您的matchrequestedWord 并检查它是null{}
  • 没关系,我去找到了 repo 并确认您的问题是缺少 useEffect 中的依赖项。我会为你整理一个答案。

标签: javascript reactjs react-router


【解决方案1】:

我认为您实际上会发现您的组件正在正确地重新渲染。问题在于您的 useEffect,而不是渲染问题。

在您的useEffect 中,[] 末尾有一个空的依赖项数组。通过拥有一个空的依赖项数组,您实际上是在告诉 React,什么都不会改变并导致这种副作用重新运行(事实并非如此)。如果这确实是您的愿望,并且您只希望在组件首次挂载时运行副作用,则应完全删除依赖项数组,这将导致您的代码如下所示:

useEffect(() => {
    const { requestedWord } = match?.params ?? {};
    console.log('ran effect');
    if (requestedWord) {
      (async () => {
        const data = await parseWordData(requestedWord);
        setWordData(data || null);
      })();
    } else setWordData(null);
  });

但是,您真正想要的是在每次搜索词更改时实际使该效果重新运行。将您的 useEffect 的其他所有内容保持不变,这将给您:

useEffect(() => {
    const { requestedWord } = match?.params ?? {};
    console.log('ran effect');
    if (requestedWord) {
      (async () => {
        const data = await parseWordData(requestedWord);
        setWordData(data || null);
      })();
    } else setWordData(null);
  }, [
      match
  ]);

尽管如此,上面仍然存在一些问题。也就是说,match 是一个对象,这意味着它是一个引用类型。在决定是否重新运行副作用时,React 仅进行 比较,而不是深入检查引用类型以查看它们是否相等(查看 this article 以更好地解释引用与值类型)。基本上,如果您像这样离开useEffect,您将运行效果太多,并且可能会从您的 API 中得到很多速率限制错误。

所以,你想要的最终示例是这样的:

const DictionaryEntryPage = (): JSX.Element => {
  const [wordData, setWordData] = useState<WordData[] | undefined | null>(undefined);
  const match = useRouteMatch<{ requestedWord: string }>("/dictionary/:requestedWord");
  const { requestedWord } = match?.params ?? {};

  useEffect(() => {
    console.log('ran effect');
    if (requestedWord) {
      (async () => {
        const data = await parseWordData(requestedWord);
        setWordData(data || null);
      })();
    } else setWordData(null);
  }, [
    requestedWord
  ]);

  //...
}

现在您在渲染时提取requestedWord(它是一个字符串,因此是一个值类型),而您的useEffect 只是依赖于该字符串。现在您的效果将是向 API 发出正确数量的请求并且它不会包含有关所请求单词的陈旧数据。

顺便说一句,我强烈建议您安装 eslint-plugin-react-hooks 并将其添加到您的 .eslintrc.json 并从中扩展。它有一个名为exhaustive-deps 的规则,它会发现这一点并提前警告你。

关于详尽依赖的补充阅读:https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function

【讨论】:

  • 非常感谢!我对出了什么问题感到很困惑,这为我解决了问题。我会确保将eslint-plugin-react-hooks 包添加到我的项目中。
猜你喜欢
  • 2021-11-10
  • 2018-11-28
  • 1970-01-01
  • 2019-09-09
  • 2021-07-19
  • 1970-01-01
  • 2020-08-12
  • 2023-03-19
  • 1970-01-01
相关资源
最近更新 更多