我们正在尝试将具有一组特定键和值的 Record 映射到特定的 React 组件。这些值可以是任意的,但所有值都必须具有一type
组特定类型的属性。
我们已经走了很长一段路来构造我们的值,我们可以通过它的type
属性来查找值类型。我们的想法是,如果我们能做到这一点,我们就可以使用该type
属性,查找值类型并将该值类型映射到特定的 React 组件。
但它没有用。
我想我可以通过将代码与解释性注释混合在一起来最好地解释这个问题,所以我将继续使用这里的代码块:
👉您可以在此处找到作为 TypeScript Playground 的完整代码。
import React from "react"
/**
* This is our data modeling
*
* The goal here is to create a base model (called "Item") that can be extended with additional
* data but must implement the basic Item fields (which the property "type").
* We also wanted to have a map of the types that we have to their models. The idea for this was
* to make it work for us to map types of models to their specific variants
*/
// Here we have a couple of base types
type ItemType = "t1" | "t2" | "t3"
// We have a base model "item" that has a field "type" which is of ItemType
interface Item<T extends ItemType> {
type: T;
}
// This defines a "Record<>" that maps every ItemType as a key to an Item of that type as a value
export type RecordOfItems = { [K in ItemType]: Item<K> };
// This helper lets us extend the basic RecordOfItems in a typesafe way:
// With this we can extend the RecordOfItems and make sure each key is still an
// ItemType and the value is an extension of the according Item.
// You cannot add other keys and you cannot use other values.
export type Items<T extends RecordOfItems> = T;
// This is the specific implementation of the RecordOfItems, we have t1-t3 as keys,
// each one is mapping to an item but extending it with their own props
type ItemMap = Items<{
t1: { type: "t1"; foo: "foo" };
t2: { type: "t2"; bar: "bar" };
t3: { type: "t3"; baz: "baz" };
}>;
// Now these were definitions of the possible data that we have, the
// data structure that we have is an array of items. The reason we create an
// ItemMap and now have an array of its values is the idea that we could map
// the ItemTypes which we can find in the `type` field to the according definition
// of the model when we read the data and need to look up which array item implements
// which model
const data: ItemMap[keyof ItemMap][] = [
{ type: "t1", foo: "foo" },
{ type: "t2", bar: "bar" },
{ type: "t3", baz: "baz" },
{ type: "t2", bar: "bar" },
{ type: "t1", foo: "foo" },
{ type: "t2", bar: "bar" },
{ type: "t2", bar: "bar" },
{ type: "t3", baz: "baz" },
]
/**
* This is our react implementation where we map our data to react components
*/
// We create another Record that maps the item types to React components
type ComponentMap = { [T in ItemType]: React.VFC<{ item: ItemMap[T] }> }
// This is the implementation of that
const componentMap: ComponentMap = {
t1: ({ item }) => <p>{ item.foo }</p>,
t2: ({ item }) => <p>{ item.bar }</p>,
t3: ({ item }) => <p>{ item.baz }</p>,
}
// Now all of that type foo bar was to make this work: get an item, look
// into its type and return the according react components and pass the item to it
// The idea was that TypeScript should understand that when we get an item of the type T
// TS could understand that there is a corresponding Component for that that matches the item
function getElement<T extends ItemType>(item: Item<T>) {
const Component = componentMap[item.type];
return <Component item={item} />;
}
// but it does not work. the Component cannot be properly mapped, TS does not even
// seem to be convinced the `Component` we get is one of the components of the map
// and cannot map it to the item as a prop