使 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>
}
)
};
我想我可能需要在这里改变我的思维方式,所以我在这里也愿意接受这些类型的答案。
考虑以下简化示例:
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>
}
)
};