TypeScript 自定义 useStateIfMounted React Hook - 并非类型 'T | 的所有组成部分 ((value: SetStateAction<T>) => void)' 可调用

inb*_*33n 2 typescript reactjs react-hooks

完整错误:

Not all constituents of type 'T | ((value: SetStateAction<T>) => void)' are callable.
Type 'T' has no call signatures.
Run Code Online (Sandbox Code Playgroud)

概括:

我正在尝试创建一个useStateIfMounted自定义挂钩。它工作得很好,但我似乎无法为 TypeScript 正确输入它。钩子很好,但是调用者在尝试调用 setState() 时收到错误。我应该如何输入 useCallback() 以便获得 TypeScript 良好的输入系统。args 应该与 React 中的常规 setState 完全相同。

我的代码:

/**
 * Same as useState, but the setState will not run if component is unmounted.
 * 
 * The setState is safe to use in dependency arrays because useCallback() has empty dependencies.
 */
export function useStateIfMounted<T>(initialState: T | (() => T)) {
    const componentUnmountedRef = useRef(false);
    const [state, _setState] = useState<T>(initialState);

    // TEMP: Line below will result in error if we don't type as 'any'
    const setState: any = useCallback((value: SetStateAction<T>) => {
        if (!componentUnmountedRef?.current) {
            _setState(value);
        }
    }, [])

    useEffect(() => {
        return () => {
            componentUnmountedRef.current = true;
        }
    }, [])

    return [state, setState]
}
Run Code Online (Sandbox Code Playgroud)

然后,当用户尝试调用 时setState(),TypeScript 将显示错误。我能解决这个问题的唯一方法是暂时将回调 (useCallback) 设置为“任意”,但这并不能让我很好地输入允许作为参数输入的内容。请参阅下面的示例了解错误是如何发生的:

---REDACTED---
const [someState, setSomeState] = useState<CustomType>(null)
---REDACTED---

setSomeState(SomeCustomObject) // <-- This line displays TypeScript errors
Run Code Online (Sandbox Code Playgroud)

我尝试过的:

  1. const setState = useCallback((value: SetStateAction<T>) => { ... }
  2. const setState: Dispatch<SetStateAction<T>> = useCallback((value: SetStateAction<T>) => { ... }
  3. const setState = useCallback((value: T | ((prevState: T) => T) => { ... }
  4. const setState = useCallback((value: any) => { ... }<-- 这个很好奇,因为我本来期望 TypeScript 允许我传递任何参数。

免责声明:所有这些都是我自己的代码,但名称是我从这个 repo中得到的。他们没有用 TypeScript 编写代码,所以我要求正确输入代码。我原来的名字是useStateUnmountedAsync,但我更喜欢他们的名字。我发现了几个其他自定义钩子,它们本质上是相同的想法,但没有一个是用 TypeScript 编写的。

编辑:添加我的 tsconfig.json 和版本 tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "downlevelIteration": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "allowUnreachableCode": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "**/node_modules/**",
    "build/**",
    "public/**"
  ]
}
Run Code Online (Sandbox Code Playgroud)

版本:

"react": "^17.0.1",
"react-scripts": "4.0.3",
"typescript": "^4.2.3",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
Run Code Online (Sandbox Code Playgroud)

law*_*itt 5

以元组形式输入返回值:

return [state, setState] as const;
Run Code Online (Sandbox Code Playgroud)

您需要指定这一点,否则两个state和都是实例setState类型,并且该联合中只有一部分是可调用的。boolean | ((value: React.SetStateAction<boolean>) => void)该元组确保状态位于索引 0,setter 位于索引 1。

这本质上是多余的,但为了获得相同的类型,useState您也可以考虑显式键入您的setState

const setState: React.Dispatch<SetStateAction<T>>
Run Code Online (Sandbox Code Playgroud)