reactjs 如何将typescript泛型约束与useState一起使用

bnl4lu3b  于 2022-12-12  发布在  React
关注(0)|答案(1)|浏览(108)

我正在学习Typescript,并且有一个类似于下面的自定义React钩子:

import { useEffect, useState } from 'react';

type TypeOne = {
  one: string;
};
type TypeTwo = {
  two: number;
};
type TypeThree = {
  three: {
    some: string;
  };
}
type AnyPropertyWithString = {
  [index: string]: string | AnyPropertyWithString;
};

export function getContent(condition: string): Promise<AnyPropertyWithString> {
  return Promise.resolve({ one: 'content' });
}

export default function useContent<T extends AnyPropertyWithString>(
  initial: T,
  condition: string
): T {
  const [content, setContent] = useState<T>(initial);

  useEffect(() => {
    async function fetchData() {
      const data = await getContent(condition);
      setContent(data);
    }
    fetchData();
  }, [condition]);

  return content;
}

我的意图是最初提供不同类型的数据给这个钩子,并将其保存在一个状态中。然后在某些条件下获取新的数据并替换状态。我想将提供给钩子的类型限制为AnyPropertyWithString。我想使用泛型,因为我将提供的类型可能有不同的属性和多个级别。返回类型应该与泛型相同。
我希望这样使用它:

const one: TypeOne = { one: 'content' };
const two: TypeTwo = { two: 2 };
const three: TypeThree = { three: { some: '3' } }

const firstResult = useContent(one, 'condition'); // ok
const secondResult = useContent(two, 'condition'); // expected TS error
const thirdResult = useContent(three, 'condition'); // ok

但是,当我尝试setContent(data)时,我有一个错误:

Argument of type 'AnyPropertyWithString' is not assignable to parameter of type 'SetStateAction<T>'.
  Type 'AnyPropertyWithString' is not assignable to type 'T'.
    'AnyPropertyWithString' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'AnyPropertyWithString'.

我不想将它转换为T,因为我已经读到这是一个不好的做法。
我怀疑问题是getContent返回的数据类型无法有效匹配,但我认为泛型约束会将T缩小到AnyPropertyWithString(由getContent返回)。我尝试了useState<T | AnyPropertyWithString>,但返回类型有问题。我还尝试了extends的不同组合,但都不起作用。
请您解释一下为什么我会出现此错误,以及如果可能的话,我如何解决它?
我真的很感激你能提供的任何帮助。

vnzz0bqm

vnzz0bqm1#

getContent如何知道T是什么,然后以T的格式返回数据?
执行此操作时:

const hookReturnValue = useContent({ foo: string }, 'condition')

那么useContent中的T类型是{ foo: string },它是AnyPropertyWithString的子类型。getContent返回与T完全不同的子类型。
因此,如果运行此代码,则初始值会将T设置为{ foo: string },然后效果会运行,并且您会将{ one: string }保存为state,这是一个不兼容的类型。
那么您的代码将执行以下操作:

const hookReturnValue = useContent({ foo: string }, 'condition')
hookReturnValue.foo.toUpperCase() // crash when effect completes

以下是此错误的含义:
'AnyPropertyWithString'可指派给型别'T'的条件约束,但'T'可以用条件约束'AnyPropertyWithString'的不同子类型执行严修化。
目前还不清楚getContent将如何工作,但它需要知道在运行时返回哪种数据格式,而您没有向它传递任何可以让它弄清楚这一点的信息。
因此,如果你弄清楚getContent将如何返回与挂钩的initial参数类型相同的类型,那么答案将开始显现。

相关问题