reactjs - 根据数据中的类型字段将数据数组映射到特定的反应组件
问题描述
我们正在尝试将具有一组特定键和值的记录映射到特定的反应组件。这些值可以是任意的,但所有值都必须具有type
来自特定类型集的属性。
type
我们已经走了很长一段路来构建我们的值,以便我们可以通过它的属性来查找值类型。我们的想法是,如果我们能做到这一点,我们可以使用type
属性,查找值类型并将该值类型映射到特定的反应组件。
但它没有用。
我想我可以通过将代码与解释性注释混合来最好地解释这个问题,所以我将从这里继续使用代码块:
你可以在这里找到作为 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
解决方案
我花了一点时间)
首先,item: Item<T>
不能作为 Component 的 props,因为它只有type
props。Item
类型不包含foo
//bar
baz
我添加了一个回调getElement
- 因为键入其他所有内容要容易得多。
我还添加了 ItemMap - const
这是我的解决方案:
import React from "react"
/**
* This is our data modelling
*
* 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" };
}>;
// If you want pass only Item as argument, we should also have a map
const ItemMap:ItemMap = {
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" },
]
type Data = typeof data
/**
* 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>,
}
function foo<T extends ItemType>(item: Item<T>, callback: (comp: ComponentMap[T], props: ItemMap[T]) => JSX.Element): JSX.Element {
const comp = componentMap[item.type];
return callback(comp, ItemMap[item.type])
}
const Comp = foo({ type: 't1' }, (Comp, props) => <Comp item={props} />) // ok
const Comp2 = foo({ type: 't1' }, (Comp, props) => <Comp item={{ type: "t1", bar: "foow" }} /> //error
推荐阅读
- prolog - 将列表从 findall 谓词转换为字符串序言
- c++ - 当文件通过 SMTP 发送时,它会丢失一些字节。C++
- elasticsearch - 有什么方法可以限制弹性搜索只匹配最接近的令牌?[边缘 n-gram,模糊性]
- amazon-web-services - AWS Kinesis SQL 的问题 - 随机森林砍伐算法
- python - 获取excel链接数据到python
- python - 我的 WordCloud 缺少单词末尾的字母 's'
- swift - 如何使用 DataRequest 在 alamofire 中设置超时
- ruby - 如何计算迭代/步骤的数量以找到方法的答案 - RUBY
- pandas - 获取每组变量分位数的值
- python - Plotly 图在 HTML 页面中显示为空白