使 react-router-dom v6 传递路径作为渲染元素的键

Make react-router-dom v6 pass path as key to rendered element

我想我可能需要在这里改变我的思维方式,所以我在这里也愿意接受这些类型的答案。

考虑以下简化示例:

export const App = (p: { apiClient: SomeApiClient }) => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/posts/:postId" element={<Post apiClient={p.apiClient} />} />
    </Routes>
  );
}

export const Home = () => {
  return <h1>Home!</h1>
}

export const Post = (p: { apiClient: SomeApiClient }) => {
  const { postId } = useParams();
  const [ state, setState ] = useState<PostState>({ status: "loading" });

  // When the component mounts, get the specified post from the API
  useEffect(() => {
    if (state.status === "loading") {
      (async () => {
        const post = await p.apiClient.getPost(postId);
        setState({ status: "ready", post });
      })();
    }
  })

  return (
    <h2>Posts</h2>
    {
      state.status === "loading"
      ? <p>Loading....</p>
      : <div className="post">
        <h3>{state.post.title}</h3>
        <div className="content">{state.post.content}</div>
      </div>
    }
  )
}

export type PostState =
  | { status: "loading" }
  | { status: "ready"; post: BlogPost };
export type BlogPost = { title: string; content: string };

这第一次工作正常,但假设页面上有一个 <Link /> 转到下一个 post。当我单击 link 时,URL 发生了变化,但页面内容没有变化,因为 React Router 实际上并没有重新安装 <Post .../> 组件。该组件正确接收更新后的 postId 并 重新呈现 ,但由于它没有 重新安装 useEffect 逻辑不再 运行,内容保持不变。

我一直在通过创建像这样的中间组件来非常笨拙地解决这个问题:

export const App = (p: { apiClient: SomeApiClient }) => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/posts/:postId" element={<PostRenderer apiClient={p.apiClient} />} />
    </Routes>
  );
}

export const PostRenderer = (p: { apiClient: SomeApiClient }) => {
  const { postId } = useParams();
  return <Post key={postId} postId={postId} apiClient={p.apiClient} />
}

export const Post = (p: { postId: string; apiClient: SomeApiClient }) => {
  // ....
}

但我开始得到很多这样的东西,实际上它们所做的只是从 URL 中获取参数并将其用作实际目标组件的键。我已经通读了 react-router-dom 文档,但没有找到任何表明有自动化方法的信息。我一定是想错了....任何建议都表示赞赏。

我认为更常见和实用的解决方案是将 postId 添加为 useEffect 的依赖项,以便在路由参数更改时重新运行异步逻辑。

示例:

export const Post = (p: { apiClient: SomeApiClient }) => {
  const { postId } = useParams();
  const [state, setState] = useState<PostState>({ status: "loading" });

  // When the post id updates, get the specified post from the API
  useEffect(() => {
    const fetchPostById = async (postId) => {
      setState({ status: "loading" });
      const post = await p.apiClient.getPost(postId);
      setState({ status: "ready", post });
    };

    fetchPostById(postId);
  }, [postId]);

  return (
    <h2>Posts</h2>
    {
      state.status === "loading"
      ? <p>Loading....</p>
      : <div className="post">
        <h3>{state.post.title}</h3>
        <div className="content">{state.post.content}</div>
      </div>
    }
  )
};