簡體   English   中英

TypeScript:基於描述任意字段及其所需類型的泛型類型的記錄樣式類型

[英]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,下面是一個沙箱,上面的所有代碼都拼湊在一起。 謝謝!

編輯 nice-carson-uk58n

編輯

正如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[]

有關完整示例,請查看沙箱: 編輯 nice-carson-uk58n

為后代:

// 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 Waynejcalz的建議,橡皮擦並保持我的例子直截了當!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM