简体   繁体   English

TypeScript:基于描述任意字段及其所需类型的泛型类型的记录样式类型

[英]TypeScript: Record-style type based on generic type describing arbitrary fields and their required types

I'm trying to express my types so that when I call my code like so:我试图表达我的类型,以便当我这样调用我的代码时:

type Topics = {
  documentList: Document[];
};

const TopicsContainer = useTopicsContainer<Topics>(['documentList']);

the array passed in as argument to useTopicsContainer is constrained to the fields defined in Topics .作为参数传递给useTopicsContainer的数组被限制在Topics中定义的字段。 An example implementation of useTopicsContainer could be as follows: 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;
};

I'd like to derive the TopicsState type from the generic TopicsTypes .我想从泛型TopicsTypes派生TopicsState类型。 So far I've tried:到目前为止,我已经尝试过:

type TopicsState<TopicTypes> = Record<
  keyof TopicTypes,
  TopicTypes[keyof TopicTypes]
>;

but that yields a TS2322: Type '{}' is not assignable to type 'TopicsState '.但这会产生TS2322: Type '{}' is not assignable to type 'TopicsState '. for const initialState: TopicsState<TopicsTypes> = {};对于const initialState: TopicsState<TopicsTypes> = {};

Another attempt:另一种尝试:

interface TopicsState<TopicTypes> {
  [topic: keyof TopicTypes]: TopicTypes[keyof TopicTypes];
}

…but that results in TS1023: An index signature parameter type must be either 'string' or 'number'. …但这会导致TS1023: An index signature parameter type must be either 'string' or 'number'. for the topic key.topic键。 I'm running out of ideas… if you'd like to give this one a go, below is a sandbox with all of the above code cobbled together.我的想法已经不多了……如果你想给这个 go,下面是一个沙箱,上面的所有代码都拼凑在一起。 Thanks!谢谢!

编辑 nice-carson-uk58n

Edit编辑

As suggested by Alex Wayne , adding Partial to TopicsState allows an empty object literal to be a TopicsState .正如Alex Wayne所建议的,将Partial添加到TopicsState允许一个空的 object 文字成为TopicsState I've updated the CodeSandbox example with that.我已经用它更新了 CodeSandbox 示例。 The only remaining issue is that now I cannot assign a value to a field in topicsState , getting Type '{ [k: string]: Document; }' is not assignable to type 'Partial<Record<keyof TopicsTypes, TopicsTypes[keyof TopicsTypes]>>'.ts(2345)唯一剩下的问题是,现在我无法为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) . Type '{ [k: string]: Document; }' is not assignable to type 'Partial<Record<keyof TopicsTypes, TopicsTypes[keyof TopicsTypes]>>'.ts(2345) On the bright side, the type checks on the consumer side in App.tsx work exactly as expected:从好的方面来说, App.tsx中消费者端的类型检查完全符合预期:

const TopicsContainer = useTopicsContainer<Topics>(["documentList"]);
const documentList: Document[] = TopicsContainer.documentList || [];

If I change documentList: Document[] to documentList: string , I get the desired Type 'Document[]' is not assignable to type 'string'.ts(2322) .如果我将documentList: Document[]更改为documentList: string ,我会得到所需的Type 'Document[]' is not assignable to type 'string'.ts(2322) Is there a way to relax the constraint in the implementation of useTopicsContainer ?有没有办法放松useTopicsContainer实施中的约束?

The type I was after goes as follows:我追求的类型如下:

type ValueOf<T> = T[keyof T];
type TopicsState<TopicTypes> = Partial<
  Record<keyof TopicTypes, ValueOf<TopicTypes>>
>;

with it, I can now use it like so:有了它,我现在可以像这样使用它:

type Topics = {
  documentList: Document[];
};
const TopicsContainer = useTopicsContainer<Topics>(["documentList"]);
const documentList: Document[] = TopicsContainer.documentList || [];

This way, TopicsContainer knows that under the key of "documentList" the type is Document[] .这样, TopicsContainer知道"documentList"键下的类型是Document[]

For a full example checkout the sandbox:有关完整示例,请查看沙箱: 编辑 nice-carson-uk58n

For posterity:为后代:

// 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>
  );
}

Thanks to Alex Wayne and jcalz for the suggestions, rubber ducking and keeping my examples straight!感谢Alex Waynejcalz的建议,橡皮擦并保持我的例子直截了当!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM