如何在TypeScript中正确注入接口上的属性?

xa9qqrwz  于 2023-05-23  发布在  TypeScript
关注(0)|答案(1)|浏览(198)

bounty还有3天到期。回答此问题可获得+200声望奖励。Lance正在寻找典型答案

我不知道为什么我的注入不起作用,在我的HaltList接口上进行类型检查。正在寻找一个解决方案,以便我可以从库外部对该接口进行类型检查。
我以前做过几次,我知道这就是样式化组件“主题”的工作方式,你可以定义自己的主题属性,它会在样式化组件内部自动完成这些属性。你基本上是这样做的:

  1. import { MyTheme } from '~/configurations'
  2. declare module 'styled-components' {
  3. interface DefaultTheme extends MyTheme {}
  4. }

./configurations/index.ts中,我们有:

  1. const theme = {
  2. fonts: {
  3. font1: 'foo'
  4. },
  5. colors: {
  6. red: 'bar'
  7. }
  8. }
  9. export type MyTheme = typeof theme

这个DefaultTheme正在被我的主题从外部动态扩展。所以样式化组件库中的所有内容都可以根据我的主题进行键入。
那么,当我尝试从头开始实现这个功能时,为什么它不起作用呢?
下面是我所做的,我创建了一个TypeScript library,它是1个文件,像这样:

  1. import { CustomError } from 'ts-custom-error'
  2. export class Halt extends CustomError {
  3. form: keyof HaltList
  4. link: HaltLink
  5. static list: HaltList = {}
  6. // make your codes cool.
  7. static code: (code: number) => string = (code: number) =>
  8. code.toString(16).padStart(4, '0').toUpperCase()
  9. // your template for rendering codes.
  10. static make: (code: string, note: string) => string = (
  11. code: string,
  12. note: string,
  13. ) => `[${code}] ${note}`
  14. constructor(form: keyof HaltList, link: HaltLink = {}) {
  15. if (!(form in Halt.list)) {
  16. throw new Error(`Name ${form} missing from halt list`)
  17. }
  18. const hook = Halt.list[form]
  19. if (!hook) {
  20. throw new Error(`Hook ${form} is missing from halt list`)
  21. }
  22. const note =
  23. typeof hook.note === 'function' ? hook.note(link) : hook.note
  24. const code = Halt.code(hook.code)
  25. const text = Halt.make(code, note)
  26. super(text)
  27. Object.defineProperty(this, 'form', {
  28. enumerable: false,
  29. value: form,
  30. })
  31. Object.defineProperty(this, 'link', {
  32. enumerable: false,
  33. value: link,
  34. })
  35. this.form = form
  36. this.link = link
  37. Object.defineProperty(this, 'name', { value: 'Halt' })
  38. }
  39. toJSON() {
  40. const hook = Halt.list[this.form]
  41. if (!hook) {
  42. throw new Error()
  43. }
  44. const note =
  45. typeof hook.note === 'function' ? hook.note(this.link) : hook.note
  46. const code = Halt.code(hook.code)
  47. const term = hook.term
  48. return { code, note, term }
  49. }
  50. }
  51. export type HaltHook = {
  52. code: number
  53. hint?: string | ((link: HaltLink) => string)
  54. note: string | ((link: HaltLink) => string)
  55. term?: Array<string> | ((link: HaltLink) => Array<string>)
  56. }
  57. export type HaltLink = Record<string, unknown>
  58. // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
  59. export interface HaltList {
  60. [key: string]: HaltHook
  61. }
  62. export function assertHalt(x: unknown): asserts x is Halt {
  63. if (!isHalt(x)) {
  64. throw new Error('Error is not a halt. ' + (x as Error).message)
  65. }
  66. }
  67. export function isHalt(x: unknown): x is Halt {
  68. return x instanceof Halt
  69. }
  70. export default function halt(
  71. form: keyof HaltList,
  72. link: HaltLink = {},
  73. ) {
  74. throw new Halt(form, link)
  75. }

在这里,我有HaltList,我想动态注入属性,所以当我这样做的时候,我得到了TypeScript的代码完成:

  1. halt('

它应该显示我在自定义代码中定义的属性,可能看起来像这样(在./configurations/errors中):

  1. import { Halt } from '@lancejpollard/halt.js'
  2. const HALT = {
  3. missing_property: {
  4. code: 1,
  5. note: 'Property missing',
  6. },
  7. }
  8. export type HaltType = typeof HALT
  9. Halt.list = HALT

然后我尝试像这样注入它,在我的项目的基础上使用一个test.d.ts文件:

  1. import { HaltType } from './configurations/errors'
  2. declare module '@lancejpollard/halt.js' {
  3. export interface HaltList extends HaltType {}
  4. }

然而,它似乎并没有做任何事情。我怎么才能得到这个,以便HaltList现在有我的missing_property,所以当我执行halt('时,它会自动完成missing_property(如果你尝试使用扩展接口中没有定义的东西,它会抛出错误)?

更新

我的用法是这样的:

  1. import { Halt } from '@lancejpollard/halt.js'
  2. const HALT = {
  3. invalid_form: {
  4. code: 3,
  5. note: ({ name }) => `Form '${name}' is not valid`,
  6. },
  7. invalid_type: {
  8. code: 2,
  9. note: ({ name, type }) => `Value '${name}' is not '${type}' type`,
  10. },
  11. missing_property: {
  12. code: 1,
  13. note: ({ name }) => `Property '${name}' missing`,
  14. },
  15. }
  16. export { Halt }
  17. export type HaltType = typeof HALT
  18. Halt.list = HALT

然后在另一个文件中:

  1. throw new Halt('missing_property')
  2. // or
  3. halt('missing_property')

这在halt('missing_的自动完成方面起作用),但它在Halt.list = HALT抛出一个错误,说:

  1. Type '{ invalid_form: { code: number; host: string; note: ({ name }: { name: any; }) => string; }; invalid_type: { code: number; host: string; note: ({ name, type }: { name: any; type: any; }) => string; }; missing_property: { code: number; host: string; note: ({ name }: { ...; }) => string; }; }' is not assignable to type 'HaltList'.
  2. Property 'invalid_form' is incompatible with index signature.
  3. Type '{ code: number; host: string; note: ({ name }: { name: any; }) => string; }' is not assignable to type 'HaltHook'.
  4. Types of property 'note' are incompatible.
  5. Type '({ name }: { name: any; }) => string' is not assignable to type 'string | ((link: HaltLink) => string)'.
  6. Type '({ name }: { name: any; }) => string' is not assignable to type '(link: HaltLink) => string'.
  7. Types of parameters '__0' and 'link' are incompatible.
  8. Property 'name' is missing in type 'HaltLink' but required in type '{ name: any; }'.

所以我试着这样做:

  1. import { Halt, HaltList } from "@lancejpollard/halt.js";
  2. const HALT: HaltList = {
  3. // ... rest

现在自动完成功能中断并说:
“string”类型的参数不能分配给“never”类型的参数。
位于halt('missing_property')的位置。

li9yvcax

li9yvcax1#

通过键入

  1. type a = HaltList['missing_property']
  2. // ^?

你可以确保类型合并 * 确实有效 *(如果不是,写在注解中)
你的问题是你不能只得到keyof (Record<string, V> & { k: V1 }),因为你只得到string
要得到你想要的keyof,而不是keyof,你需要删除索引签名:
https://tsplay.dev/Nr93zw

  1. // import type { OmitIndexSignature } from 'type-fest' // doesn't work on Playground, so
  2. type OmitIndexSignature<ObjectType> = {
  3. [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
  4. ? never
  5. : KeyType]: ObjectType[KeyType];
  6. };
  7. export default function halt(
  8. form: keyof OmitIndexSignature<HaltList>
  9. ) {}
  10. type a = Exclude<keyof HaltList, 'missing_property'>
  11. // ^? type a = string | number
  12. type b = HaltList['missing_property']
  13. // ^? type b = { code: number; note: string; }
  14. halt('')
  15. // ^| missing_property
  16. ////////////////////////////////////////////////
  17. // file A.ts
  18. const HALT = {
  19. missing_property: {
  20. code: 1,
  21. note: 'Property missing',
  22. },
  23. }
  24. export type HaltType = typeof HALT
  25. // declare module '@me/lib/B.ts'
  26. export interface HaltList extends HaltType {}
  27. // }
  28. ////////////////////////////////////////////////
  29. // fine B.ts
  30. export type HaltHook = {
  31. code: number
  32. hint?: string | ((link: URL) => string)
  33. note: string | ((link: URL) => string)
  34. term?: Array<string> | ((link: URL) => Array<string>)
  35. }
  36. export interface HaltList {
  37. [key: string]: HaltHook
  38. }
  39. ////////////////////////////////////////////////
展开查看全部

相关问题