打字稿:API 获取类型错误:“对象可能为‘空’。”

时间:2021-02-27 05:50:48

标签: reactjs typescript

我正在将我的 React 应用程序重构为 Typescript,到目前为止一切顺利。我在我的最后一个组件上,那个获取 API 数据的组件,我试图理解为什么这些类型错误没有解决。我使用 https://jvilk.com/MakeTypes/ 来帮助正确搭建接口。

Object is possibly 'null' 两次用于 errorstories(下面的注释行)
Parameter '___' implicitly has an 'any' type. 两次用于 storyidx(下面的注释行)

我在使用此 API 调用组件时遇到问题。我错过了什么?

import React, { FC, ReactElement, useEffect, useState } from "react";
import Story from "./Story";

export interface NewsProps {
  results?: (ResultsEntity)[] | null;
}
export interface ResultsEntity {
  section: string;
  title: string;
  abstract: string;
  url: string;
  multimedia?: (MultimediaEntity)[] | null;
}
export interface MultimediaEntity {
  url: string;
  caption: string;
}

const News: FC<NewsProps> = ({results:ResultsEntity}):ReactElement => {
  const [error, setError] = useState(null);
  const [stories, setStory] = useState(null);

  useEffect(() => {
    const getCurrentPage = () => {
      const url = new URL(window.location.href);
      const page = url.pathname.split("/").pop();
      return page ? page : "home";
    };
    const section = getCurrentPage();
    fetch(
      `https://api.nytimes.com/svc/topstories/v2/${section}.json?api-key=4fzCTy6buRI5xtOkZzqo4FfEkzUVAJdr`
    )
      .then((res) => res.json())
      .then((data) => {
        setTimeout(() => setStory(data), 1500);
      })
      .catch((error) => {
        console.log("Error", error);
        setError(error);
      });
  }, []);

  if (error) {
    return <div>Error: {error.message}</div>;   /// "Object is possibly 'null'" (for error)
  } else 
  if (!stories) {
    return <div>Loading...</div>
  } else {
    return (
      <>
        <ul className="stories">
          {stories.results.map((story, idx) => { // "Object is possibly 'null'" (for stories)
                                                 // Parameter '__' implicitly has an 'any' type (for idx & story)
            return (
              <Story
                key={idx}
                title={story.title}
                abstract={story.abstract}
                img={
                  story &&
                  story.multimedia &&
                  story.multimedia[0] &&
                  story.multimedia[0].url
                    ? story.multimedia[0].url
                    : null
                }
                alt={
                  story &&
                  story.multimedia &&
                  story.multimedia[0] &&
                  story.multimedia[0].caption
                    ? story.multimedia[0].caption
                    : null
                }
                link={story.url}
              />
            );
          })}
        </ul>
      </>
    );
  }
}
export default News;

2 个答案:

答案 0 :(得分:0)

在您的代码中,有两个地方 TS 编译器不知道某些值的确切类型,并警告您您已尝试对它们执行(可能)无效的操作。

添加 | null 也无济于事,因为这不是问题的原因,而添加 (unless you are using the --strictNullChecks flag) null is assignable to any type by default,因此无需输入。

问题点:

  1. error.message

    尽管您检查了 error 的真实性,但编译器无法推断它不可能是 null。而且,当然,访问 null 上的属性会出错,因此会发出警告。

    要解决这个问题,您可以使用non-null assertion operator

     error!.message
    
  2. stories

    由于该值是根据网络请求动态设置的,因此 TS 不会知道它的类型(它无法从默认值推断它,因为它是 null)。这导致它是 any,并且编译器也不知道对它的操作(例如它不知道 .map()Array#map(),所以也不知道类型它将传递给其回调的参数)。

    要解决此问题,您必须定义 stories 的确切类型(接口)(及其属性,递归)。然后,将该接口传递给 useState,以定义状态变量的类型。

    但是,由于 stories 有时也是 null,因此在正确定义其类型后,您必须在访问属性之前使用非空断言,其推理与上述相同。

总而言之,您的代码应如下所示:

import React, { FC, ReactElement, useEffect, useState } from "react";
import Story from "./Story";

export interface NewsProps{
  //Props of the component go here
  //Don't confuse them with state or other variables
}
interface Result {
  results: ResultsEntity[];
}
interface ResultsEntity {
  section: string;
  title: string;
  abstract: string;
  url: string;
  multimedia?: MultimediaEntity[];
}
interface MultimediaEntity {
  url: string;
  caption: string;
}

const News:FC<NewsProps> = ():ReactElement => {
  const [error, setError] = useState<Error | null>(null);
  const [stories, setStory] = useState<Result | null>(null);

  useEffect(() => {
    const getCurrentPage = () => {
      const url = new URL(window.location.href);
      const page = url.pathname.split("/").pop();
      return page ? page : "home";
    };
    const section = getCurrentPage();
    fetch(
      `https://api.nytimes.com/svc/topstories/v2/${section}.json?api-key=4fzCTy6buRI5xtOkZzqo4FfEkzUVAJdr`
    )
      .then((res) => res.json())
      .then((data) => {
        setTimeout(() => setStory(data), 1500);
      })
      .catch((error) => {
        console.log("Error", error);
        setError(error);
      });
  }, []);

  if (error) {
    //                       v-- Non-null assertion (it can't be null, you've checked beforehand)
    return <div>Error: {error!.message}</div>;
  } else 
  if (!stories) {
    return <div>Loading...</div>
  } else {
    return (
      <>
        <ul className="stories">
          //      v-- Non-null assertion (it can't be null, you've checked beforehand)
          {stories!.results.map((story, idx) => {
            return (
              <Story
                key={idx}
                title={story.title}
                abstract={story.abstract}
                img={
                  //              vv-- It's enough to do this (optional chaining, new JS feature). Much more readable. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
                  story.multimedia?.[0].url ?? null
                  //   ^^^^^^^^^^^             ^^^^-- Fallback value
                  //   +++++++++++--  Thing that may be null or undefined
                }
                alt={
                  story.multimedia?.[0].caption ?? null //Same as above
                }
                link={story.url}
              />
            );
          })}
        </ul>
      </>
    );
  }
}
export default News;

See it live 在 TS Playground 中(不会产生错误)

答案 1 :(得分:0)

打字稿不理解null的类型

const [error, setError] = useState<string>(null);