为什么这些元组类型是联合的



我在一个全新的Create React App repo中重新创建了错误,源代码可以在这里找到。

正如您在MyContext.tsx中看到的,我只是将useState的结果添加到我的上下文中。上下文的类型也被定义为元组

const UDContext = createContext<
| undefined
| [
UserData | undefined,
React.Dispatch<React.SetStateAction<UserData | undefined>>
]
>(undefined);

然而,当我尝试通过访问MyComponent.tsx中的数据时

const [userData] = useUserData();

我收到错误

Property 'username' does not exist on type 'UserData | Dispatch<SetStateAction<UserData | undefined>>'.

在我看来,这似乎是在以某种方式将元组的类型组合为一?

有趣的是:下面的代码确实有效(它确实发出了context可能未定义的警告,这确实有道理(。

const context = useUserData();
const userData = context[0];

MySetter.tsx中也发生了完全相同的情况,其中的确切错误是

Not all constituents of type 'UserData | Dispatch<SetStateAction<UserData | undefined>>' are callable.

上述";解决方案";而不直接破坏在这里也有效。

实现这一点的唯一方法是显式声明useUserData的返回值的类型。

一些附带说明:

  • 这是Create React应用程序的@next版本
  • 我必须启用--downlevelIteration,否则useUserData()的销毁将产生Type '[UserData | undefined, Dispatch<SetStateAction<UserData | undefined>>] | undefined' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.
  • 我在CodeSandbox中有一个工作示例,它没有任何问题

这似乎是typescript中的BUG,我发现了两个有类似问题的打开的BUG 31613和BUG 40215

除了您在问题中提到的选项(返回时显式类型(之外,另一个选项是不使用元组析构函数,对于您的代码,它看起来像:

MyComponent.tsx

import React from "react";
import { useUserData } from "./MyContext";
const MyComponent = () => {
const userData = useUserData();
if (!userData) {
return <div>Loading username</div>;
}
return <div>{userData[0]?.username}</div>;
};
export default MyComponent;

MySetter.tsx

import { useEffect } from "react";
import { useUserData } from "./MyContext";
const MySetter = () => {
const userData = useUserData();
// Fakes an API call
useEffect(() => {
setTimeout(() => {
userData && userData[1]({ username: "FooBar" });
}, 750);
});
return null;
};
export default MySetter;

这是因为您的上下文也可能是未定义的,所以您需要进行检查,以确保始终从上下文中返回内容。您可以创建可重复使用的方法来创建总是的东西的上下文,而不是undefined:

function createCtx<T>() {
const Context = React.createContext<T | undefined>(undefined);
const useCtx = () => {
const ctx = React.useContext(Context);
if (typeof ctx === "undefined") {
throw new Error("useContext must be inside provider with a value.");
}
return ctx;
};
return [useCtx, Context.Provider] as const;
};

然后在可能导出useUserDataUserDataContext的地方使用这种方法

const [useUserData, UserDataProvider] = createCtx<[
UserData | undefined,
React.Dispatch<React.SetStateAction<UserData | undefined>>
]>();
const UserDataContext: React.FC = ({ children }) => {
const [userData, setUserData] = React.useState<UserData>();
return (
<UserDataProvider value={[userData, setUserData]}>
{children}
</UserDataProvider>
);
};

并在组件中使用它

export default function App() {
const [userData] = useUserData();

return (
<h2>{userData?.username}</h2>
);
}

这不仅解决了您的问题,还为您提供了一种可重复使用的方法,可以帮助您完成其他您肯定需要的事情。

干杯。

最新更新