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 ? &lt;ProjectDetails project=state.project /&gt; : &lt;ProjectDetails project=undefined/&gt;;) 但是,它返回最后一个传递的项目,时间戳是一个普通的 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 对象而不是原始类型的主要内容,如果未能解决你的问题,请参考以下文章

去掉vue地址栏中分隔#问题

更改地址栏中的完整 URL,无需重新加载页面

React 不会在路由参数更改或查询更改时重新加载组件数据

更改 url 参数时反应路由器不重新加载组件

无法获取 API URL 以重新路由到正确的地址:React Hooks

Facebook 如何在不重新加载页面或使用 # 或的情况下更改浏览器地址栏中的 URL?