如何从通用钩子中获取特定类型?

huangapple go评论76阅读模式
英文:

How to get specific type from a generic hook?

问题

我试图创建一个通用的钩子,它连接到Firebase/Firestore并返回给定集合中的所有项。我试图通过创建一个ICollectionMap接口来指定它们的类型,以获取返回项的正确类型。目前我有以下代码:

export const useCollection = <TCollectionMap extends Record<string, any>>(
  collectionName: keyof TCollectionMap
): {
  collection: TCollectionMap[keyof TCollectionMap]
} => {
  const [collection, setCollection] = useState<TCollectionMap[keyof TCollectionMap>>([])

  useEffect(() => {
    // 在这里从Firebase获取数据
    ...
    const data = ...
    setCollection(data)
  }, [])

  return { collection }
}

现在假设我们有以下类型:

interface INames { name: string }

interface IAges { age: number }

interface ICollectionMap {
  names: INames[]
  ages: IAges[]
}

那么当我使用这个钩子时:

const { collection } = useCollection<ICollectionMap>('names')

我希望看到collection的类型是INames[],但是TypeScript 声明它是INames[] | IAges[]。是否有一种正确的方式来在TypeScript中处理这个问题?

英文:

I am trying to create a generic hook that connects to Firebase/Firestore and returns all items from a given collection. I am trying to get the correct type of returned items by creating an ICollectionMap interface specifying their types. This is what I have so far:

export const useCollection = &lt;TCollectionMap extends Record&lt;string, any&gt;&gt;(
  collectionName: keyof TCollectionMap
): {
  collection: TCollectionMap[keyof TCollectionMap]
} =&gt; {
  const [collection, setCollection] = useState&lt;TCollectionMap[keyof TCollectionMap]&gt;([])

  useEffect(() =&gt; {
    // fetch data from Firebase here
    ...
    const data = ...
    setCollection(data)
  }, [])

  return { collection }
}

So then, let's say we have the following types:

interface INames { name: string }

interface IAges { age: number }

interface ICollectionMap {
  names: INames[]
  ages: IAges[]
}

Now when I use the hook:

  const { collection } = useCollection&lt;ICollectionMap&gt;(&#39;names&#39;)

I'd like to see the type of collection to be INames[], but TypeScript claims it is INames[] | IAges[]. Is there a proper way to handle it with TS?

答案1

得分: 1

以下是翻译好的部分:

"在一个函数中执行是可能的,如下所示:"
"这带来了一个明显的限制,即您必须指定所有的类型参数。这是 TypeScript 尚未具备部分类型推断的结果。"
"是否有特殊原因需要这样抽象的 useCollection 函数?考虑以下情况:"
"但是,如果这不是一个答案,请考虑下一个答案。"
"为了解决部分类型推断的缺失,我们可以使用另一种方式,只需将您的函数包装在另一个泛型函数中:"
"请注意,这一层抽象仅用于实现我们自己的一种部分类型推断。"
"作为一个附带的提示:如果您选择这条路线,我建议在 React 组件之外创建 createdICollection = createCollection<ICollectionMap>(),关于这个问题可以通过这个出色的答案得到更多信息:"
"简而言之:"
"在大多数情况下,您应该在组件函数之外声明函数,这样您只需声明一次,并始终重复使用相同的引用。如果您在内部声明函数,每次渲染组件时都会重新定义函数。"

英文:

While it is possible to do in one function like so:

export const useCollection = &lt;TCollectionMap extends Record&lt;string, any&gt;, K extends keyof TCollectionMap&gt;(
  collectionName: K
): {
  collection: TCollectionMap[K]
} =&gt; null!
const { collection } = useCollection&lt;ICollectionMap, &#39;names&#39;&gt;(&#39;names&#39;)
//   ^? collection: INames[]

It comes with the express limitation that you have to specify all type parameters. This is a consequence of typescript not yet having partial type inferencing.

Don't use a generic TCollectionMap (?)

Is there any particular reason you need such an abstract useCollection function? Consider the following

export const useICollection = &lt;K extends keyof ICollectionMap&gt;(
  collectionName: K
): {
  collection: ICollectionMap[K]
} =&gt; null!
const { collection } = useICollection(&#39;names&#39;)
//   ^? collection: INames[]

However, if this is a non-answer consider the next answer

Wrapping your function with another function

To get around the lack of partial type inferencing we can use another way, just wrap your function with another generic function

export const createCollection = &lt;TCollectionMap extends Record&lt;string, any&gt;&gt;() =&gt; &lt;K extends keyof TCollectionMap&gt;(
  collectionName: K
): {
  collection: TCollectionMap[K]
} =&gt; null!
const useICollection = createCollection&lt;ICollectionMap&gt;()
const { collection } = useICollection(&#39;names&#39;)
//   ^? collection: INames[]

Note that this layer of abstraction is purely used for the sake of implementing our own sort of partial type inferencing.

As a side node: if you do go this route, I recommend creating createdICollection = createCollection&lt;ICollectionMap&gt;() outside the React component, some more reading about this can be answered by this excellent answer to the question: Where should functions in function components go?
In short:

> In most cases you should declare the functions outside the component function so you declare them only once and always reuse the same reference. When you declare the function inside, every time the component is rendered the function will be defined again.

View all Examples on TS Playground

huangapple
  • 本文由 发表于 2023年7月24日 01:04:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76749434.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定