“未定义”类型上不存在属性“句柄” - 反应上下文和打字稿

问题描述 投票:0回答:1

我正在将我的应用程序从 JS 转换为 TS。在 JS 下,一切都运行良好,但是当开始转换为 TS 时,我在处理函数(例如handleVideoAdd)时遇到了大量错误。有谁知道我做错了什么?尝试了很多方法都没有成功...

类型“未定义”上不存在属性“handleVideoAdd”。 TS2339 - 它指出了这段代码:

    const { handleVideoAdd, inputURL, handleInputURLChange } =  useContext(Context)

我的代码看起来像这样:

  • 标头.tsx

    import { Context } from "../Context";
    import React, { useContext } from "react";
    import { Navbar, Button, Form, FormControl } from "react-bootstrap";

export default function Header() {
  const { handleVideoAdd, inputURL, handleInputURLChange } =
    useContext(Context);
  return (
    <Navbar bg="light" expand="lg">
      <Navbar.Brand href="#home">Video App</Navbar.Brand>
      <Form onSubmit={handleVideoAdd} inline>
        <FormControl
          type="text"
          name="url"
          placeholder="Paste url"
          value={inputURL}
          onChange={handleInputURLChange}
          className="mr-sm-2"
        />
        <Button type="submit" variant="outline-success">
          Add
        </Button>
      </Form>
    </Navbar>
  );
}
  • 上下文.tsx
import { useEffect, useMemo, useState } from "react";
import { youtubeApi } from "./APIs/youtubeAPI";
import { vimeoApi } from "./APIs/vimeoAPI";
import React from "react";
import type { FormEvent } from "react";

const Context = React.createContext(undefined);

function ContextProvider({ children }) {
  const [inputURL, setInputURL] = useState("");
  const [videoData, setVideoData] = useState(() => {
    const videoData = localStorage.getItem("videoData");
    if (videoData) {
      return JSON.parse(videoData);
    }
    return [];
  });
  const [filterType, setFilterType] = useState("");
  const [videoSources, setVideoSources] = useState([""]);
  const [wasSortedBy, setWasSortedBy] = useState(false);
  const [showVideoModal, setShowVideoModal] = useState(false);
  const [modalData, setModalData] = useState({});
  const [showWrongUrlModal, setShowWrongUrlModal] = useState(false);

  const createModalSrc = (videoItem) => {
    if (checkVideoSource(videoItem.id) === "youtube") {
      setModalData({
        src: `http://www.youtube.com/embed/${videoItem.id}`,
        name: videoItem.name,
      });
    } else {
      setModalData({
        src: `https://player.vimeo.com/video/${videoItem.id}`,
        name: videoItem.name,
      });
    }
  };

  const handleVideoModalShow = (videoID) => {
    createModalSrc(videoID);
    setShowVideoModal(true);
  };
  const handleVideoModalClose = () => setShowVideoModal(false);

  const handleWrongUrlModalShow = () => setShowWrongUrlModal(true);

  const handleWrongUrlModalClose = () => setShowWrongUrlModal(false);

  const handleInputURLChange = (e) => {
    setInputURL(e.currentTarget.value);
  };

  const handleVideoAdd = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const source = checkVideoSource(inputURL);

    if (source === "youtube") {
      handleYouTubeVideo(inputURL);
    } else if (source === "vimeo") {
      handleVimeoVideo(inputURL);
    } else {
      handleWrongUrlModalShow();
    }
  };

  const checkVideoSource = (inputURL) => {
    if (inputURL.includes("youtu") || inputURL.length === 11) {
      return "youtube";
    } else if (inputURL.includes("vimeo") || inputURL.length === 9) {
      return "vimeo";
    }
  };

  const checkURL = (inputURL) => {
    if (!inputURL.includes("http")) {
      const properURL = `https://${inputURL}`;
      return properURL;
    } else {
      return inputURL;
    }
  };

  const checkInputType = (inputURL) => {
    if (!inputURL.includes("http") && inputURL.length === 11) {
      return "id";
    } else if (!inputURL.includes("http") && inputURL.length === 9) {
      return "id";
    } else {
      return "url";
    }
  };

  const fetchYouTubeData = async (videoID) => {
    const data = await youtubeApi(videoID);
    if (data.items.length === 0) {
      handleWrongUrlModalShow();
    } else {
      setVideoData((state) => [
        ...state,
        {
          id: videoID,
          key: `${videoID}${Math.random()}`,
          name: data.items[0].snippet.title,
          thumbnail: data.items[0].snippet.thumbnails.medium.url, //default, medium, high
          viewCount: data.items[0].statistics.viewCount,
          likeCount: data.items[0].statistics.likeCount,
          savedDate: new Date(),
          favourite: false,
          source: "YouTube",
          url: inputURL,
        },
      ]);
      setInputURL("");
    }
  };

  const handleYouTubeVideo = (inputURL) => {
    const inputType = checkInputType(inputURL);

    if (inputType === "id") {
      fetchYouTubeData(inputURL);
    } else {
      const checkedURL = checkURL(inputURL);
      const url = new URL(checkedURL);
      if (inputURL.includes("youtube.com")) {
        const params = url.searchParams;
        const videoID = params.get("v");
        fetchYouTubeData(videoID);
      } else {
        const videoID = url.pathname.split("/");
        fetchYouTubeData(videoID[1]);
      }
    }
  };

  const fetchVimeoData = async (videoID) => {
    const data = await vimeoApi(videoID);

    if (data.hasOwnProperty("error")) {
      handleWrongUrlModalShow();
    } else {
      setVideoData((state) => [
        ...state,
        {
          id: videoID,
          key: `${videoID}${Math.random()}`,
          name: data.name,
          thumbnail: data.pictures.sizes[2].link, //0-8
          savedDate: new Date(),
          viewCount: data.stats.plays,
          likeCount: data.metadata.connections.likes.total,
          savedDate: new Date(),
          favourite: false,
          source: "Vimeo",
          url: inputURL,
        },
      ]);
      setInputURL("");
    }
  };

  const handleVimeoVideo = (inputURL) => {
    const inputType = checkInputType(inputURL);

    if (inputType === "id") {
      fetchVimeoData(inputURL);
    } else {
      const checkedURL = checkURL(inputURL);
      const url = new URL(checkedURL);
      const videoID = url.pathname.split("/");
      fetchVimeoData(videoID[1]);
    }
  };

  const deleteVideo = (key) => {
    let newArray = [...videoData].filter((video) => video.key !== key);
    setWasSortedBy(true);
    setVideoData(newArray);
  };

  const deleteAllData = () => {
    setVideoData([]);
  };

  const toggleFavourite = (key) => {
    let newArray = [...videoData];
    newArray.map((item) => {
      if (item.key === key) {
        item.favourite = !item.favourite;
      }
    });
    setVideoData(newArray);
  };

  const handleFilterChange = (type) => {
    setFilterType(type);
  };

  const sourceFiltering = useMemo(() => {
    return filterType
      ? videoData.filter((item) => item.source === filterType)
      : videoData;
  }, [videoData, filterType]);

  const sortDataBy = (sortBy) => {
    if (wasSortedBy) {
      const reversedArr = [...videoData].reverse();
      setVideoData(reversedArr);
    } else {
      const sortedArr = [...videoData].sort((a, b) => b[sortBy] - a[sortBy]);
      setWasSortedBy(true);
      setVideoData(sortedArr);
    }
  };

  const exportToJsonFile = () => {
    let dataStr = JSON.stringify(videoData);
    let dataUri =
      "data:application/json;charset=utf-8," + encodeURIComponent(dataStr);

    let exportFileDefaultName = "videoData.json";

    let linkElement = document.createElement("a");
    linkElement.setAttribute("href", dataUri);
    linkElement.setAttribute("download", exportFileDefaultName);
    linkElement.click();
  };

  const handleJsonImport = (e) => {
    e.preventDefault();
    const fileReader = new FileReader();
    fileReader.readAsText(e.target.files[0], "UTF-8");
    fileReader.onload = (e) => {
      const convertedData = JSON.parse(e.target.result);
      setVideoData([...convertedData]);
    };
  };

  useEffect(() => {
    localStorage.setItem("videoData", JSON.stringify(videoData));
  }, [videoData]);

  return (
    <Context.Provider
      value={{
        inputURL,
        videoData: sourceFiltering,
        handleInputURLChange,
        handleVideoAdd,
        deleteVideo,
        toggleFavourite,
        handleFilterChange,
        videoSources,
        sortDataBy,
        deleteAllData,
        exportToJsonFile,
        handleJsonImport,
        handleVideoModalClose,
        handleVideoModalShow,
        showVideoModal,
        modalData,
        showWrongUrlModal,
        handleWrongUrlModalShow,
        handleWrongUrlModalClose,
      }}
    >
      {children}
    </Context.Provider>
  );
}
export { ContextProvider, Context };

  • App.js(尚未转换为 TS)
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import reportWebVitals from "./reportWebVitals";
import { ContextProvider } from "./Context.tsx";

ReactDOM.render(
  <React.StrictMode>
    <ContextProvider>
      <App />
    </ContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

javascript reactjs typescript react-context react-hook-form
1个回答
1
投票

这是因为当您创建上下文时,您将其默认为

undefined

这发生在这里:

const Context = React.createContext(undefined)

你不能说

undefined.handleVideoAdd
。但理论上你可以说
{}.handleVideoAdd

因此,如果您在开始时将上下文默认为

{}
,如下所示:
const Context = React.createContext({})

您的应用程序不应再预先崩溃。

编辑:我看到您正在使用 TypeScript,在这种情况下,您将需要为您的上下文创建一个界面。像这样的东西:

interface MyContext {
  inputURL?: string,
  videoData?: any,
  handleInputURLChange?: () => void,
  handleVideoAdd?: () => void,
  deleteVideo?: () => void,
  // and all the rest of your keys
}

然后在创建上下文时执行以下操作:

const Context = React.createContext<MyContext>({});
© www.soinside.com 2019 - 2024. All rights reserved.