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

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

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

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

import { MyTheme } from '~/configurations'

declare module 'styled-components' {
  interface DefaultTheme extends MyTheme {}
}

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

const theme = {
  fonts: {
    font1: 'foo'
  },
  colors: {
    red: 'bar'
  }
}

export type MyTheme = typeof theme

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

import { CustomError } from 'ts-custom-error'

export class Halt extends CustomError {
  form: keyof HaltList

  link: HaltLink

  static list: HaltList = {}

  // make your codes cool.
  static code: (code: number) => string = (code: number) =>
    code.toString(16).padStart(4, '0').toUpperCase()

  // your template for rendering codes.
  static make: (code: string, note: string) => string = (
    code: string,
    note: string,
  ) => `[${code}] ${note}`

  constructor(form: keyof HaltList, link: HaltLink = {}) {
    if (!(form in Halt.list)) {
      throw new Error(`Name ${form} missing from halt list`)
    }

    const hook = Halt.list[form]

    if (!hook) {
      throw new Error(`Hook ${form} is missing from halt list`)
    }

    const note =
      typeof hook.note === 'function' ? hook.note(link) : hook.note
    const code = Halt.code(hook.code)
    const text = Halt.make(code, note)

    super(text)

    Object.defineProperty(this, 'form', {
      enumerable: false,
      value: form,
    })

    Object.defineProperty(this, 'link', {
      enumerable: false,
      value: link,
    })

    this.form = form
    this.link = link

    Object.defineProperty(this, 'name', { value: 'Halt' })
  }

  toJSON() {
    const hook = Halt.list[this.form]

    if (!hook) {
      throw new Error()
    }

    const note =
      typeof hook.note === 'function' ? hook.note(this.link) : hook.note
    const code = Halt.code(hook.code)
    const term = hook.term

    return { code, note, term }
  }
}

export type HaltHook = {
  code: number
  hint?: string | ((link: HaltLink) => string)
  note: string | ((link: HaltLink) => string)
  term?: Array<string> | ((link: HaltLink) => Array<string>)
}

export type HaltLink = Record<string, unknown>

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export interface HaltList {
  [key: string]: HaltHook
}

export function assertHalt(x: unknown): asserts x is Halt {
  if (!isHalt(x)) {
    throw new Error('Error is not a halt. ' + (x as Error).message)
  }
}

export function isHalt(x: unknown): x is Halt {
  return x instanceof Halt
}

export default function halt(
  form: keyof HaltList,
  link: HaltLink = {},
) {
  throw new Halt(form, link)
}

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

halt('

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

import { Halt } from '@lancejpollard/halt.js'

const HALT = {
  missing_property: {
    code: 1,
    note: 'Property missing',
  },
}

export type HaltType = typeof HALT

Halt.list = HALT

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

import { HaltType } from './configurations/errors'

declare module '@lancejpollard/halt.js' {
  export interface HaltList extends HaltType {}
}

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

更新

我的用法是这样的:

import { Halt } from '@lancejpollard/halt.js'

const HALT = {
  invalid_form: {
    code: 3,
    note: ({ name }) => `Form '${name}' is not valid`,
  },
  invalid_type: {
    code: 2,
    note: ({ name, type }) => `Value '${name}' is not '${type}' type`,
  },
  missing_property: {
    code: 1,
    note: ({ name }) => `Property '${name}' missing`,
  },
}

export { Halt }

export type HaltType = typeof HALT

Halt.list = HALT

然后在另一个文件中:

throw new Halt('missing_property')
// or
halt('missing_property')

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

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'.
  Property 'invalid_form' is incompatible with index signature.
    Type '{ code: number; host: string; note: ({ name }: { name: any; }) => string; }' is not assignable to type 'HaltHook'.
      Types of property 'note' are incompatible.
        Type '({ name }: { name: any; }) => string' is not assignable to type 'string | ((link: HaltLink) => string)'.
          Type '({ name }: { name: any; }) => string' is not assignable to type '(link: HaltLink) => string'.
            Types of parameters '__0' and 'link' are incompatible.
              Property 'name' is missing in type 'HaltLink' but required in type '{ name: any; }'.

所以我试着这样做:

import { Halt, HaltList } from "@lancejpollard/halt.js";

const HALT: HaltList = {
// ... rest

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

li9yvcax

li9yvcax1#

通过键入

type a = HaltList['missing_property']
//   ^?

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

// import type { OmitIndexSignature } from 'type-fest' // doesn't work on Playground, so
type OmitIndexSignature<ObjectType> = {
    [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
        ? never
        : KeyType]: ObjectType[KeyType];
};

export default function halt(
  form: keyof OmitIndexSignature<HaltList>
) {}

type a = Exclude<keyof HaltList, 'missing_property'>
//   ^? type a = string | number
type b = HaltList['missing_property']
//   ^? type b = { code: number; note: string; }

halt('')
//    ^| missing_property

////////////////////////////////////////////////
// file A.ts
const HALT = {
  missing_property: {
    code: 1,
    note: 'Property missing',
  },
}
export type HaltType = typeof HALT
// declare module '@me/lib/B.ts'
  export interface HaltList extends HaltType {}
// }
////////////////////////////////////////////////
// fine B.ts
export type HaltHook = {
  code: number
  hint?: string | ((link: URL) => string)
  note: string | ((link: URL) => string)
  term?: Array<string> | ((link: URL) => Array<string>)
}
export interface HaltList {
  [key: string]: HaltHook
}
////////////////////////////////////////////////

相关问题