Typescript: ошибки типа выборки API: «Объект, возможно, «нулевой»».

#reactjs #typescript

#reactjs #typescript

Вопрос:

Я рефакторингую свое приложение React на Typescript и пока все хорошо. Я работаю над своим последним компонентом, тем, который извлекает данные API, и я пытаюсь понять, почему эти ошибки типа не разрешаются. Я использовал https://jvilk.com/MakeTypes / чтобы помочь правильно настроить интерфейсы.

Object is possibly 'null' дважды для error amp; stories (прокомментированные строки ниже)
Parameter '___' implicitly has an 'any' type. дважды для story amp; idx (прокомментированные строки ниже)

У меня возникли проблемы с этим компонентом вызова 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 amp; story)
            return (
              <Story
                key={idx}
                title={story.title}
                abstract={story.abstract}
                img={
                  story amp;amp;
                  story.multimedia amp;amp;
                  story.multimedia[0] amp;amp;
                  story.multimedia[0].url
                    ? story.multimedia[0].url
                    : null
                }
                alt={
                  story amp;amp;
                  story.multimedia amp;amp;
                  story.multimedia[0] amp;amp;
                  story.multimedia[0].caption
                    ? story.multimedia[0].caption
                    : null
                }
                link={story.url}
              />
            );
          })}
        </ul>
      </>
    );
  }
}
export default News;
 

Ответ №1:

Typescript не понимает тип null

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

Ответ №2:

В вашем коде есть два места, где компилятор TS не знает точных типов некоторых значений и предупреждает вас, что вы пытались сделать (возможно) недопустимые вещи с ними.

Добавление | null также не помогает, поскольку это не причина проблемы, и (если вы не используете --strictNullChecks флаг) null по умолчанию присваивается любому типу, поэтому вводить его не нужно.

Проблемные моменты:

  1. error.message

    Хотя вы проверяете правдивость error , компилятор не может сделать вывод, что этого не может быть null . И, конечно, обращение к свойству null было бы ошибкой, отсюда и предупреждение.

    Чтобы решить эту проблему, вы можете использовать оператор утверждения, отличный от null:

      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;
 

Посмотрите его в режиме реального времени на игровой площадке TS (он не выдает ошибок)

Комментарии:

1. Хорошо, я обновил свой код и добавил больше типов, если это поможет отобразить все возвращаемые данные. Я все еще получаю те же ошибки.

2. @Christian Я включил пример кода. Проверьте это, оно прокомментировано и не выдает ошибок TS. Надеюсь, теперь все стало понятнее.