【问题标题】:Typescript can't tell I'm returning early if param is undefined如果 param 未定义,Typescript 无法告诉我提前返回
【发布时间】:2021-05-07 04:03:16
【问题描述】:

我正在将 TypeScript 与 React 一起使用,我很困惑为什么 TS 无法判断如果 previousundefined 时下面的钩子会提前返回。它抱怨previous.length(“对象可能未定义”),因为它认为以前可能是undefined,但事实并非如此,因为如果是这样,函数会提前返回

  function ReactComponent() {
      const previous = usePrevious(value)

      React.useEffect(() => {
        if (previous === undefined) {
           return
        }

        if (previous.length) { // TS says "Object is possibly undefined" despite the return gate above
   
        }
    
      }, [previous])

      return null
  }

奇怪的是,这只是 React 钩子内部的一个问题。下面的代码不会抱怨...

const bar: Array<number> | undefined = undefined

function foo() {
  if (bar === undefined) {
    return
  }

  if (bar.length) { // TS does NOT complain here
    
  }
}

我的快速修复想法是使用非空断言运算符 (!) 所以 previous!.length 但这也抱怨“在类型 never 上不存在属性长度”

编辑:这是usePrevious 挂钩的代码。

export default function usePrevious(value: any) {
  const ref = React.useRef()

  React.useEffect(() => {
    ref.current = value
  }, [value])

  return ref.current
}

我可以通过明确声明此函数的返回类型为any 来解决此问题。换句话说,通过将function usePrevious(value: any) { ... } 更改为function usePrevious(value: any): any {...}

为什么这会修复它对我来说仍然是一个谜,所以我暂时保持开放状态。

【问题讨论】:

  • 可选链接运算符是否修复它:previous?.length?不知道为什么它在一种情况下抱怨而不是另一种情况
  • previous?.length 抱怨“'never' 类型上不存在属性 'length'”。同previous!.length
  • value 是如何声明的?
  • 在您的情况下,usePrevious 的返回类型不是总是未定义吗?

标签: reactjs typescript react-hooks


【解决方案1】:

usePrevious 的 ReturnType 始终为 undefined。尝试将其键入:

function usePrevious<T>(value: T): T | undefined {
  const ref = React.useRef<T>();
  
  React.useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

【讨论】:

  • 谢谢这很好用。我刚刚意识到我应该使用这样的泛型
【解决方案2】:

该死的,这和我刚刚写的 @Federkun 很相似。

import { useRef, useEffect, MutableRefObject } from 'react';
type CustomNonNullable<T> = T extends null | undefined
    ? never
    : T;
// if generic parameter <T> is a function Type, it extracts the return type
type ReturnTypeOf<T extends (...args: any) => any> = T extends (
    ...args: any
) => infer U
    ? U
    : any;
export default function usePrevious<T, P>(
    value: T,
    previousValue: P
) {
    const refMutable: MutableRefObject<T | undefined> = useRef<T>(); // refMutable: MutableRefObject<T | undefined>
    const refPrevious: MutableRefObject<P | undefined> = useRef<P>(); // refInitial: RefObject<T>
    // use nonullable ! operator to override refMutable.current conditionally undefined
    const NonnullableValue: CustomNonNullable<T> = refMutable!.current! ?? value!;
    const NonnullablePreviousValue: CustomNonNullable<P> = refPrevious!.current! ?? previousValue!;
    useEffect(() => {
        refMutable.current = value ?? undefined;
        refPrevious.current = previousValue ?? undefined;
    }, [value, previousValue]);
    return { NonnullablePreviousValue, NonnullableValue };
}

let y: ReturnTypeOf<typeof usePrevious>;
/**
 * @var y inferred
    let y: {
    NonnullablePreviousValue: unknown;
    NonnullableValue: unknown;
}
 */

export const inferredYObject = () => {
    const { NonnullablePreviousValue, NonnullableValue } = y;
    const trulyPrivateObject = Object.freeze({
        NonnullablePreviousValue,
        NonnullableValue
    } as const);
    return trulyPrivateObject;
};

/**
 * @function inferredYObject has the following type
 *Readonly<{
    readonly NonnullablePreviousValue: unknown;
    readonly NonnullableValue: unknown;
}>
 * this makes it safe at runtime -- prevents XSSmodification
 */

【讨论】:

    猜你喜欢
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 2015-02-07
    • 2014-02-10
    • 1970-01-01
    • 2019-03-27
    • 2019-04-11
    相关资源
    最近更新 更多