类型 Promise<any> 上不存在 TS 属性

时间:2021-04-02 12:53:56

标签: reactjs typescript async-await

目前正在尝试呈现从 URL 获取的数据。如果我在文件本身中 console.log 它,它工作正常。但是现在我想在页面组件上调用获取的数据,这样我就可以将其渲染为 TSX。

这是我获取数据的 API 文件,名为 api.ts:

export const getVideoInfo = async () => {
  try {
    const getVideos = await fetch(
      "url"
    );
    const videos = await getVideos.json();
    return videos;
  } catch (e) {
    console.log(e);
  }
};

然后还有另一个文件(这里我尝试在url中找到一个与hash匹配的文件,名为useCourseDetail.ts:

import { useLocation } from "react-router";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";

export const FindCourseDetail = async () => {
  const { hash } = useLocation();
  const slugifiedHash = Slugify(hash);
  const data = await getVideoInfo();


  if (hash) {
    const video = data.videos.find(
      (v) =>
        Slugify(v.title, {
          lowerCase: true,
          replaceDot: "-",
          replaceAmpersand: "and",
        }) === slugifiedHash
    );
    return video;

  } else {
    return data.videos[0];

  }
};

并且在两个文件中我都得到了我想要的对象。现在我想用对象里面的内容在页面文件中渲染一些tsx,叫做:CourseDetail.tsx。

import { FindCourseDetail } from "./FindCourseDetail";

type Video = {
  title: string;
  description?: string;
  thumbnail?: string;
  videoid?: string;
  chapter: boolean;
  duration: number;
  subtitles: [];
};

export const CourseDetail = () => {
  const videoObject = FindCourseDetail(); 


  return (
    <div>
      <h2>Content player</h2>
      <h2>{videoObject.title}</h2>
    </div>
  );
};

并且 'videoObject.title' 会给我错误: 'Promise' 类型不存在属性 'title'。

我认为这是公平的,因为如果我控制台.log 它,它是一个承诺。但我不确定如何编写这样的东西并使其工作。所以它应该是这样的:const videoObject = await FindCourseDetail(); 。但这是不可能的,因为该组件不是异步的,而是直接从 react 路由器调用的。

我尝试将 useCourseDetail 的功能复制粘贴到 CourseDetail 中。如果我使用 useState 和 useEffect,这会起作用。但这有点乱,感觉不太好,因为默认状态是一个带有 null 的对象。见下面的代码:

import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";

type Video = {
  title: string;
  description?: string;
  thumbnail?: string;
  videoid?: string;
  chapter: boolean;
  duration: number;
  subtitles: [];
};

export const CourseDetail = () => {
  const { hash } = useLocation();

  const [videoData, setVideoData] = useState<Video>({
    title: null,
    description: null,
    thumbnail: null,
    videoid: null,
    chapter: null,
    duration: null,
    subtitles: null,
  });

  useEffect(() => {
   findCourseData();
  }, []);

   const findCourseData = async () => {
     const slugifiedHash = Slugify(hash);
     const data = await getVideoInfo();

     if (hash) {
       const video = data.videos.find(
         (v) =>
           Slugify(v.title, {
             lowerCase: true,
             replaceDot: "-",
             replaceAmpersand: "and",
           }) === slugifiedHash
       );
       setVideoData(video);
     }
   };

  return (
    <div>
      <h2>Content player</h2>
      <h2>{videoObject.title}</h2>
    </div>
  );
};

我有一种感觉,这不是一个需要解决的大问题,但我看不出哪里出了问题或如何解决这个问题。

编辑: 我尝试了以下方法:

const [newData, setNewData] = useState({});

  const getData = async () => {
    try {
      const apiValue = await FindCourseDetail();
      setNewData(apiValue);
    } catch (err) {
      // error handling
    }
  };
  getData();

return (
    <div>
      <h2>Content player</h2>
      {newData && <h2>{newData.title}</h2>}
    </div>
  );

或: FindCourseDetail().then(videoObject => setNewData(videoObject))

现在它会抛出同样的错误: 类型“{}”上不存在属性“title”。

如果我删除空对象作为默认状态,它会说:它可能是未定义的。

如果在默认状态下写空,它当然可以工作。但我认为这不是正确的方式:

const [newData, setNewData] = useState({
    title: null,
    description: null,
    thumbnail: null,
    videoid: null,
    chapter: null,
    duration: null,
    subtitles: null,
  });

编辑 2:

import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";

type Video = {
  title: string;
  description?: string;
  thumbnail?: string;
  videoid?: string;
  chapter: boolean;
  duration: number;
  subtitles: [];
};

export const CourseDetail = () => {
  const { hash } = useLocation();
  
  const [videoData, setVideoData] = useState<Video>({
    title: null,
    description: null,
    thumbnail: null,
    videoid: null,
    chapter: null,
    duration: null,
    subtitles: null,
  });

  const findCourseData = async () => {
    const slugifiedHash = Slugify(hash);
    const data = await getVideoInfo();

    if (hash) {
      const video = data.videos.find(
        (v) =>
          Slugify(v.title, {
            lowerCase: true,
            replaceDot: "-",
            replaceAmpersand: "and",
          }) === slugifiedHash
      );
      setVideoData(video);
    }
  };
  useEffect(() => {
    findCourseData();
  }, []);

  return (
    <div>
      <h2>Content player</h2>
      {videoData&& <h2>{videoData.title}</h2>}
    </div>
  );
};

这目前有效,但正如您所看到的,我将函数本身复制并粘贴到组件中。所以我尝试了以下代码:

type Video = {
  title: string;
  description?: string;
  thumbnail?: string;
  videoid?: string;
  chapter: boolean;
  duration: number;
  subtitles: [];
};

export const CourseDetail = () => {

  const [newData, setNewData] = useState<Video>(null);

  
  const getData = async () => {
    try {
      const apiValue = await FindCourseDetail();
      console.log(apiValue);
      setNewData(apiValue);
    } catch (e) {
      console.log('catch')
      console.log(e)
    }
  };

   useEffect(() => {
    getData();
  }, []);

  return (
    <div>
      <h2>Content player</h2>
      {newData&& <h2>{newData.title}</h2>}
    </div>
  );

这不会运行,catch被触发,这是错误日志:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
 1. You might have mismatching versions of React and the renderer (such as React DOM)
 2. You might be breaking the Rules of Hooks
 3. You might have more than one copy of React in the same app

不知道为什么。这是调用 FindCourseDetail 的代码:

import { useLocation } from "react-router";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";

export const FindCourseDetail = async () => {
  const { hash } = useLocation();
  const slugifiedHash = Slugify(hash);
  const data = await getVideoInfo();

  if (hash) {
    const video = data.videos.find(
      (v) =>
        Slugify(v.title, {
          lowerCase: true,
          replaceDot: "-",
          replaceAmpersand: "and",
        }) === slugifiedHash
    );
    return video;

  } else {
    return data.videos[0];

  }
};

2 个答案:

答案 0 :(得分:0)

您可能应该继续使用 useEffect 方法,但将 useState 的(通用)类型声明为联合,包括 null 或 Video 数据。然后你使用类型保护来消除它为空的情况(不要渲染它)。

像这样...

const [videoData, setVideoData] = useState<Video|null>(null);

...

  return (
    <div>
      <h2>Content player</h2>
      {videoData && <h2>{videoData.title}</h2>}
    </div>
  );

答案 1 :(得分:0)

解决了!我在 FindCourseDetail 中调用 useLocation 钩子。 我通过在当前文件中调用 useLocation 钩子来修复它,并将其作为一个道具提供给 FindCourseDetail。