React 路由器在 url 地址栏重新加载时提供 javascript 对象而不是原始类型
Posted
技术标签:
【中文标题】React 路由器在 url 地址栏重新加载时提供 javascript 对象而不是原始类型【英文标题】:React router provides javascript object instead of original type on url address bar reload 【发布时间】:2021-06-04 14:43:09 【问题描述】:我只是用相关代码来解释我的问题,因为完整的例子在thiscodesandbox 链接中。
我通过一个组件的链接传递一些道具。 这些道具有一个火力基地时间戳。 通过链接调用组件时,props 正确传递。
链接:
<Link to=
pathname:path,
state:
project
,
key=project.id>
<ProjectSummary project=project deleteCallback=projectDelete/>
</Link>
路线:
<Route
path='/project/:id'
render=( location : location: Location<project: IFirebaseProject>) =>
const state = location;
const returnedComponent = state ? <ProjectDetails project=state.project /> :
<ProjectDetails project=undefined/>;
return returnedComponent;
/>
并由ProjectList
组件接收,如下所示:
<div>moment(stateProject.createdAt.toDate()).calendar()</div>
我的问题是,当通过链接调用组件时,传递了道具并且一切正常,但是,当我在 url 地址栏中重新输入时,由于对组件的访问不是通过链接,我会期望路由的渲染返回一个未定义的项目(检查路由:
const returnedComponent = state ? <ProjectDetails project=state.project /> : <ProjectDetails project=undefined/>;
) 但是,它返回最后一个传递的项目,时间戳是一个普通的 javascript 对象而不是 Timestamp 类型。所以我得到了错误:
TypeError: stateProject.createdAt.toDate is not a function
因为toDate()
函数在返回的纯Javascript 对象中不可用,所以它是Timestamp firebase 类型。似乎对于这种特定情况,路由器将其保留为普通的 js 对象,而不是原始的 Timestamp 实例。如果没有从链接调用,我希望路由始终返回未定义的项目,因为道具没有传入(据说),但从 url 地址栏重新加载时并非如此。
奇怪的是,在codesandbox项目中,它不会重现,它会获取数据(当收到的项目未定义时,您将能够看到console.log('project fetched!!')
)。
但是它发生从开发服务器抛出。可能有事要办。 如果您想克隆并检查,请找到 git url:https://github.com/LuisMerinoP/my-app.git
记住,要重现你只需要输入链接,然后将焦点放在资源管理器的url地址栏中,然后按回车。
我认为这可能是预期的行为,也许有一种更优雅的方式来处理这种特定情况,而不是检查重新加载时返回的类型。我想知道是否可以知道它是从地址栏而不是链接调用的。
我知道我可以检查我的组件中的类型并解决这个问题,从返回的 js 对象在组件中创建一个新的时间戳,但我不希望路由器有这种行为,我想了解发生了什么。
【问题讨论】:
【参考方案1】:问题:不可序列化状态
它返回最后一个通过的项目,时间戳是一个普通的 Javascript 对象而不是时间戳类型
我不希望路由器会出现这种行为,并且想了解发生了什么。
发生了什么是state
被序列化然后反序列化,这意味着它被转换为 JSON 字符串表示并返回。您将保留除您的方法之外的任何属性。
文档可能应该对此更明确,但you should not store anything that is not serializable。在底层 React Router DOM 使用浏览器的 History API 和 those docs make it more clear。
建议
as
在 typescript 中是一个断言。它告诉编译器“使用这种类型,即使它不是真正的这种类型”。当你有真正的类型时,不要使用as
。而是将类型应用于变量:const project: IFirebaseProject =
你的 getProjectId
函数从 URL 获取 id 不是必需的,因为 React Router 已经可以做到这一点!使用useParams
钩子。
不要在 state 中复制 props。你总是想要一个“单一的事实来源”。
获取数据
我经常使用您的代码,因为起初我认为直接访问页面时您根本没有加载项目。后来我才知道你是,但那时我已经重写了一切!
您网站上的每个 URL 都需要能够自行加载,无论它是如何访问的,因此您需要一些机制来仅从 id
加载适当的项目数据。为了最大限度地减少获取,您可以将项目存储在共享父 App
的状态中,在 React 上下文中,或通过像 Redux 这样的全局状态。 Firestore 有一些我不太熟悉的内置缓存机制。
由于您现在使用的是虚拟占位符数据,因此您希望构建一种访问数据的方式,以便以后替换您的真实方式。我正在创建一个钩子useProject
,它接受id
并返回项目。稍后只需用更好的钩子替换那个钩子!
import IFirebaseProject from "../types";
import projects from "./sample-data";
/**
* hook to fetch a project by id
* might initially return undefined and then resolve to a project
* right now uses dummy data but can modify later
*/
const useProject_dummy = (id: string): IFirebaseProject | undefined =>
return projects.find((project) => project.id === id);
;
import IFirebaseProject from "../types";
import useState, useEffect from "react";
import db from "./db";
/**
* has the same signature so can be used interchangeably
*/
const useProject_firebase = (id: string): IFirebaseProject | undefined =>
const [project, setProject] = useState<IFirebaseProject | undefined>();
useEffect(() =>
// TODO: needs a cleanup function
const get = async () =>
try
const doc = await db.collection("projects").doc(id).get();
const data = doc.data();
//is this this right type? Might need to manipulate the object
setProject(data as IFirebaseProject);
catch (error)
console.error(error);
;
get();
, [id]);
return project;
;
您可以将单个项目页面的呈现与与从 URL 获取项目相关的逻辑分开。
const RenderProjectDetails = ( project : project: IFirebaseProject ) =>
return (
<div className="container section project-details">
...
const ProjectDetailsScreen = () =>
// get the id from the URL
const id = useParams< id: string >();
// get the project from the hook
const project = useProject(id ?? "");
if (project)
return <RenderProjectDetails project=project />;
else
return (
<div>
<p> Loading project... </p>
</div>
);
;
Code Sandbox Link
【讨论】:
这个答案如此完整和有帮助真是太神奇了。实际上,我花了很长时间才把它扔掉并意识到提供的所有新技巧。非常感谢您的回答,不仅解释了让我发疯的问题中发生的事情,而且还提供了所有适当的方法来处理特定的情况,捕获数据而不是通过指向路线的链接传递道具.希望我能多投票几十次。 是的,这绝对是可能的!通常我使用 Redux 来存储我的数据实体。我会在useProject
钩子中使用 Redux useSelector
钩子来查找现有数据,并且仅在它不存在时才获取。在幕后 Redux 使用 React Contexts 使数据随处可用。老实说,我对 Firebase 不太熟悉。我知道有一些用于自动 Redux 集成的包,例如 react-redux-firebase
。
Firestore 中有一些内置的缓存功能:firebase.google.com/docs/firestore/manage-data/enable-offline
非常感谢!!在询问之前,我删除了 cmets 以尝试自己:)。我正在考虑在 useProjectList
钩子中公开项目数组,使用效果监听器将保持该数组始终更新。然后我可以导入那个暴露的数组。不知道这是否可能(我的意思是将钩子数据暴露给外部范围)或者是否有意义,但我会检查一下。你的 useSelector 提议听起来很不错。我不知道:)
至于“外部范围”,基本上您可以将数据暴露给 React Context Provider 组件的所有后代的范围:reactjs.org/docs/context.html以上是关于React 路由器在 url 地址栏重新加载时提供 javascript 对象而不是原始类型的主要内容,如果未能解决你的问题,请参考以下文章