【问题标题】:extend an interface through a function call通过函数调用扩展接口
【发布时间】:2022-11-04 00:35:17
【问题描述】:

背景:我正在使用 Typescript 在 React 中构建一个国际化钩子。

我找不到将属性动态添加到interfacetype 或确保我的其他语言正确所需的任何方法的方法。

请看我的minimal playground

type Locale = "en" | "fr"

//
// I want to somehow add to this below interface (can be changed to something else)
//
export interface i18nTextMap {
    [key: string]: string | Function;
}
//
// The below should have an error since they are empty and should have the values in the function below
//
const frenchTextMap: i18nTextMap = {};

type AddToTextMapAndReturnOutput = <FuncReturn, FuncArgs>(
  text: string | ((arg0: FuncArgs) => FuncReturn),
  key: string,
  locale: Locale,
  args?: FuncArgs
) => string;


const i18n: AddToTextMapAndReturnOutput = (text, key, locale, args) => {
    //
    //.    ?????  How can I add the key to my interface (or other) as a prop with typeof text, so that my "frenchTextMap" has a typing error?
    //
    const textType = typeof text
    let outputText = text
    //. If french
    if(locale === "fr" && frenchTextMap[key]){
        outputText = frenchTextMap[key]
    }
  
    // Return my output
    if(textType === "string"){
        return text
    }
    // Not part of my question but there shouldn't be an error below
    return text(args)
}


i18n("Hello","hello", "en")     // <= console.log output: hello
i18n( ({name})=> `Greetings ${name}`,"greeting", "fr", {name: "John"}) // <= console.log output: Greetings John

你会注意到,我不知道这是否可能,我愿意接受建议,我正在寻找添加i18n 中给我的interface i18nTextMap 的任何键(或类型或任何可以工作的键),这样如果我没有frenchTextMap 的正确类型,它就会产生错误。

【问题讨论】:

  • 这个问题是否取决于 React?如果是这样,让我们​​这样标记它;如果没有,您能否从示例代码中删除依赖项?此外,虽然有一个指向 IDE 的外部链接很好,但它并不能代替问题本身中的 minimal reproducible example 作为明文。该沙盒链接中显示的错误是否与您的问题相关?如果是这样,你能谈谈他们吗?如果没有,您可以编辑示例以使其没有任何不相关的错误吗?我认为也许可以在没有第三方依赖的情况下探索“通过函数调用扩展接口”的想法,但也许你需要它们。
  • 据我所知,这可能起作用的唯一方法是,如果您正在修改现有对象的类型(通过控制流分析......所以该函数可能是一个断言函数),然后在同一范围内使用该对象的类型来定义/合并到相关接口中。类似this playground link 的节目。但是我不知道如何根据您的 i18n 内容来构建该方法,而没有看到纯文本独立的最小可重现示例。
  • 无论如何,如果您想让我写下该链接中发生的事情作为答案,或者如果您想 edit 将您问题中的示例作为独立的 minimal reproducible example 并希望我再看一下,请在任何回复中提及@jcalz,以便我收到通知。祝你好运!
  • @jcalz 感谢您的反馈。我添加了一些 React 只是为了说明另一种类型的输出,但我的问题确实不需要。刚刚重新格式化并提供了一个简化的示例。
  • @denislexic 考虑到frenchTextMap 没有“hello”键,这个i18n("Hello","hello", "fr") 是否应该抛出类型错误?您还可以在操场上添加“错误”状态吗?

标签: typescript


【解决方案1】:

在您的 AddToTextMapAndReturnOutput 类型中,您将函数参数类型与返回类型分开。虽然i18nTextMap 接口的限制较少,因为您允许Function 类型,即使签名匹配,参数类型和返回类型也不相同。

一个快速的解决方案是在类型和接口中分离参数的类型和返回类型,并像这样使用泛型:

interface FuncArgs {
    [key: string]: string,
};
type FuncReturn = string;

interface i18nTextMap<A,B> {
    [key: string]: string | ((arg0?: A) => B),
}
type AddToTextMapAndReturnOutput<A,B> = (
    text: string | ((arg0: A) => B),
    key: string,
    locale: Locale,
    args?: A,
) => B;

这样您就可以完全控制函数参数的类型及其返回类型。

然后您的代码变为:

const frenchTextMap: i18nTextMap<FuncArgs, FuncReturn> = {};

const i18n: AddToTextMapAndReturnOutput<FuncArgs, FuncReturn> = (text, key, locale, args) => {

    let outputText = text

    if(locale === "fr" && frenchTextMap[key]){
        outputText = frenchTextMap[key]
    }

    if(typeof outputText === 'function' && args){
        return outputText(args)
    }

    return outputText as string;
}

i18n("Hello","hello", "en")     // <= console.log output: hello
i18n( ({name})=> `Greetings ${name}`,"greeting", "fr", {name: "John"}) // <= console.log output: Greetings John since French isn't defined

您会注意到我们必须转换返回 outputText,因为我们的 outputText 可以是 stringfunction。通过使用联合类型,打字稿无法推断出我们的第一个 return 语句处理了后者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-18
    • 2020-06-09
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多