首页 > 解决方案 > 如何使变量具有未知类型或分配“电影”?

问题描述

我正在开发一个钩子,我可以在其中传递一个发出 Web 请求并返回的函数isLoading,dataerror.

const [isLoading, data, error] = useApi(getMovie, idMovie, someAction);

基本上我有一个useApi接收 3 个参数的钩子 ():

我这样使用它:

const idMovie = { _id: "3" };
// callback function
const someAction = (data: unknown) => {
 // do something
 return data;
};

const [isLoading, data, error] = useApi(getMovie, idMovie, someAction);

使用Api.tsx

import { AxiosPromise, AxiosResponse } from 'axios';
import { useState, useEffect } from 'react';
const useApi = (
    apiFunction: (params: unknown) => AxiosPromise,
    params = {},
    callback: (data: unknown) => void
): [boolean, unknown, null | string] => {
    const [data, setData] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState<null | string>(null);

    useEffect(() => {
        apiFunction(params)
            .then(({ data }: AxiosResponse) => {
                setData(data);
                setIsLoading(false);
                if (callback) {
                    callback(data);
                }
            })
            .catch(() => {
                setError('Something went wrong');
                setIsLoading(false);
            });
    }, []); //apiFunction, params, callback]

    return [isLoading, data, error];
};

export default useApi;

getMovie对应于解决网络请求的函数

import axios from "axios";
type getMovieParams = { _id: string };
const BASE_URL = "https://jsonplaceholder.typicode.com/todos/";
const getMovie = (params: getMovieParams): Promise<unknown> => {
  if (params) {
    const { _id } = params;
    if (_id) {
      const url = `${BASE_URL}/${_id}`;
      return axios.get(url);
    }
  }
  throw new Error("Must provide a query");
};

export default getMovie;

调用此钩子的代码如下所示:

import "./styles.css";
import useApi from "./useApi";
import getMovie from "./api";
interface Movie {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export default function App() {
  const idMovie = { _id: "3" };
  // callback function
  const someAction = (data: unknown) => {
    // do something
    return data;
  };

  const [isLoading, data, error] = useApi(getMovie, idMovie, someAction);

  const dataResponse = error ? [] : data; //type Movie
  console.log(dataResponse);
  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      {error && <div>{error}</div>}
      -- Get Data Movie 1--
      <p>{dataResponse.title}</p>
    </div>
  );
}

我在这一行遇到打字错误:

<p>{dataResponse.title}</p>

这是因为:

const dataResponse = error ? [] : data; 

data是类型unknown,这是因为它可以是任何类型data,但我想在这种情况下指定数据类型是' Movie',想法是在另一个组件中重用这个钩子并能够说出数据的类型将在此钩子返回时获得data

这是我的实时代码:

https://codesandbox.io/s/vigilant-swartz-iozn4

创建此问题的原因是寻求您的帮助以解决标有红线的打字稿问题。(文件 app.tsx)

在此处输入图像描述

如何解决?谢谢

标签: reactjstypescript

解决方案


可以使用泛型来捕获与您传递给函数的参数相关的类型。例如,在你的截图中,TS 对参数的抱怨,我们可以声明一个泛型来捕获参数类型

function useApi<Params>(
  apiFunction: (params: Params) => AxiosPromise,
  params: Params,
  callback: (data: unknown) => void
)

这样,我们的第二个钩子参数将被键入apiFunction为其 arg 中要求的任何类型。

我们做同样的事情来输入我们返回给回调的数据

function useApi<Params, Return>(
  apiFunction: (params: Params) => Promise<AxiosResponse<Return>>,
  params: Params,
  callback: (data: Return) => void
)

apiFunction我通过检查axios.get调用的类型得到了返回类型

get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;

如您所见,最后它返回一个Promise<AxiosResponse<T>>. 我们用我们的通用类型截取它T并命名它Return

现在我们也可以在钩子的返回类型和主体中使用这些类型

): [boolean, Return | null, null | string] {
  const [data, setData] = useState<Return | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<null | string>(null);

  useEffect(() => {
    apiFunction(params)
      .then(({ data }) => {
        setData(data);
        setIsLoading(false);
        if (callback) {
          callback(data);
        }
      })
      .catch(() => {
        setError("Something went wrong");
        setIsLoading(false);
      });
  }, []); //apiFunction, params, callback]

  return [isLoading, data, error];
}

由于您的 Movie 模型是特定于 getMovie 调用的,因此您可以将该界面移到那里并键入axios.get调用

interface Movie {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

const getMovie = (params: getMovieParams) => {
  if (params) {
    const { _id } = params;
    if (_id) {
      const url = `${BASE_URL}/${_id}`;
      return axios.get<Movie>(url);
    }
  }
  throw new Error("Must provide a query");
};

通过所有这些添加,当您使用钩子时,您会在代码上观察到一些警告,这将迫使您在考虑所有可能值的情况下实现渲染,我像这样重写它

  ...
  const [isLoading, dataResponse, error] = useApi(
    getMovie,
    idMovie,
    someAction
  );

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) return <div>error</div>;

  return <div>{dataResponse?.title}</div>;
  ...

https://codesandbox.io/s/generic-hook-arg-types-s8vtt


推荐阅读