[英]TypeScript: Record-style type based on generic type describing arbitrary fields and their required types
我試圖表達我的類型,以便當我這樣調用我的代碼時:
type Topics = {
documentList: Document[];
};
const TopicsContainer = useTopicsContainer<Topics>(['documentList']);
作為參數傳遞給useTopicsContainer
的數組被限制在Topics
中定義的字段。 useTopicsContainer
的示例實現如下:
const useTopicsContainer = <TopicsTypes>(
topics: (keyof TopicsTypes)[]
): TopicsState<TopicsTypes> => {
const initialState: TopicsState<TopicsTypes> = {};
const [topicsState, setTopicsState] = useState<TopicsState<TopicsTypes>>(
initialState
);
// some code mutating the state by calling setTopicsState, like e.g.
setInterval(
() =>
setTopicsState(
Object.fromEntries(
topics.map((topic) => [topic, createRandomDocument()])
)
),
1000
);
return topicsState;
};
我想從泛型TopicsTypes
派生TopicsState
類型。 到目前為止,我已經嘗試過:
type TopicsState<TopicTypes> = Record<
keyof TopicTypes,
TopicTypes[keyof TopicTypes]
>;
但這會產生TS2322: Type '{}' is not assignable to type 'TopicsState '.
對於const initialState: TopicsState<TopicsTypes> = {};
另一種嘗試:
interface TopicsState<TopicTypes> {
[topic: keyof TopicTypes]: TopicTypes[keyof TopicTypes];
}
…但這會導致TS1023: An index signature parameter type must be either 'string' or 'number'.
為topic
鍵。 我的想法已經不多了……如果你想給這個 go,下面是一個沙箱,上面的所有代碼都拼湊在一起。 謝謝!
正如Alex Wayne所建議的,將Partial
添加到TopicsState
允許一個空的 object 文字成為TopicsState
。 我已經用它更新了 CodeSandbox 示例。 唯一剩下的問題是,現在我無法為topicsState
中的字段賦值,得到Type '{ [k: string]: Document; }' is not assignable to type 'Partial<Record<keyof TopicsTypes, TopicsTypes[keyof TopicsTypes]>>'.ts(2345)
Type '{ [k: string]: Document; }' is not assignable to type 'Partial<Record<keyof TopicsTypes, TopicsTypes[keyof TopicsTypes]>>'.ts(2345)
。 從好的方面來說, App.tsx
中消費者端的類型檢查完全符合預期:
const TopicsContainer = useTopicsContainer<Topics>(["documentList"]);
const documentList: Document[] = TopicsContainer.documentList || [];
如果我將documentList: Document[]
更改為documentList: string
,我會得到所需的Type 'Document[]' is not assignable to type 'string'.ts(2322)
。 有沒有辦法放松useTopicsContainer
實施中的約束?
我追求的類型如下:
type ValueOf<T> = T[keyof T];
type TopicsState<TopicTypes> = Partial<
Record<keyof TopicTypes, ValueOf<TopicTypes>>
>;
有了它,我現在可以像這樣使用它:
type Topics = {
documentList: Document[];
};
const TopicsContainer = useTopicsContainer<Topics>(["documentList"]);
const documentList: Document[] = TopicsContainer.documentList || [];
這樣, TopicsContainer
知道"documentList"
鍵下的類型是Document[]
。
為后代:
// useTopicsContainer.ts
import { useEffect, useState } from "react";
import io from "socket.io-client";
type ValueOf<T> = T[keyof T];
type TopicsState<TopicTypes> = Partial<
Record<keyof TopicTypes, ValueOf<TopicTypes>>
>;
type KeysOfTopicsState<TopicTypes> = Extract<keyof TopicTypes, string>[];
const client = io("/topics");
const useTopicsContainer = <TopicsTypes>(
topics: KeysOfTopicsState<TopicsTypes>
): TopicsState<TopicsTypes> => {
const initialState: TopicsState<TopicsTypes> = {};
const [topicsState, setTopicsState] = useState<TopicsState<TopicsTypes>>(
initialState
);
// some code mutating the state by calling setTopicsState, like e.g.
useEffect(() => {
const topicListeners = Object.fromEntries(
topics.map((topic) => [
topic,
(topicData: ValueOf<TopicsTypes>) => {
setTopicsState((oldState) => ({
...oldState,
[topic]: topicData
}));
}
])
);
topics.forEach((topic) => {
client.on(topic as string, topicListeners[topic]);
});
return () => {
topics.forEach((topic) => {
client.off(topic, topicListeners[topic]);
});
};
});
return topicsState;
};
export default useTopicsContainer;
// App.tsx
import React from "react";
import useTopicsContainer from "./useTopicsContainer";
type Topics = {
documentList: Document[];
};
export default function App() {
const TopicsContainer = useTopicsContainer<Topics>(["documentList"]);
const documentList: Document[] = TopicsContainer.documentList || [];
console.log({ documentList });
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
感謝Alex Wayne和jcalz的建議,橡皮擦並保持我的例子直截了當!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.