我不确定这是否可能(或实际可行),但我很好奇.


我有一个包含string个变量的强类型词典,如下所示:

const variables: Variables = {
    REACT_APP_MY_VARIABLE: 'Test',
    REACT_APP_MY_BOOLEAN_VARIABLE: 'true'
};

我直接通过variables对象访问这些变量:const myVariable = variables.REACT_APP_MY_VARIABLE;等.

"问题"是,对于这些值中的一些值,string不是值的合理表示(例如'true'),所以在使用它之前,我必须以某种方式转换它.

const myBoolean = toBoolean(variables.REACT_APP_MY_BOOLEAN_VARIABLE);

这基本上没问题...但是,如果可以将转换后的值存储在一个对象中,以及在未指定转换的情况下默认存储为字符串的所有其他值,那就太好了.

我曾想过创建一种包装类来存储原始字符串,然后让一些getter来执行转换,但我希望能够自动执行这一操作,而不是 for each 值都详尽地创建getter.

class EnvironmentVariables {
    // This should actually be a private variable but my keyboard 
    // doesn't have a hash key.
    private _variables: Variables;
    constructor(variables: Variables) {
        this._variables = variables;
    }

    get myBooleanVariable() {
        return toBoolean(this._variables.REACT_APP_MY_BOOLEAN_VARIABLE);
    }
}

现在我可以很好地获得布尔值:

const environmentVariables = new EnvironmentVariables(variables);

// myBoolean = true
const myBoolean = environmentVariables.myBooleanVariable;

但是,如果我想要其他值,我必须 for each 值都创建一个getter,当最常见的情况只是返回string值时,这是相当困难的:

// I don't want to make one of these for every value.
get myVariable() {
    return this._variables.REACT_APP_MY_VARIABLE;
}

为此,我可以以编程方式为任何值添加"默认"获取方法,而不需要显式覆盖,并且只需将其映射到内部_variables集合.

constructor(variables: Variables) {
    this._variables = variables;

    for (const key in variables) {
        const getterName = key.replace('REACT_APP_', '').toCamelCase();

        if (this.hasOwnProperty(getterName)) {
            continue;
        }

        Object.defineProperty(this, getterName, {
            get: () => this._variables[key];
        });
    }
}

这真是太棒了!除了我想不出如何让TypeScrip识别出当我将一个值加到variables时,该值应该也可以通过EnvironmentVariables访问.

const environmentVariables = new EnvironmentVariables(variables);

// myBooleanVariable = true, I am happy.
const myBooleanVariable = environmentVariables.myBooleanVariable;

// myVariable = 'Test'... but TypeScript gives an error saying there's
// no 'myVariable' on type 'EnvironmentVariables'.
const myVariable = environmentVariables.myVariable;

理想情况下,我也非常希望它能够将变量名变为驼峰大小写,并删除‘REACTION_APP’前缀,因为它非常笨重.我怀疑这是否可能,但我认为这可能是模板类型和泛型可以完成的事情?

基本上,我想要的东西是这样工作的:

const variables = {
    REACT_APP_MY_VARIABLE: 'Test',
    REACT_APP_MY_BOOLEAN_VARIABLE: 'true'
};

class EnvironmentVariables {
    ... // Magic goes here.

    get myBooleanVariable() {
        return toBoolean(this._variables.REACT_APP_MY_BOOLEAN_VARIABLE);
    }
}

const environmentVariables = new EnvironmentVariables(variables);

// TypeScript allows this even though there's no getter defined in
// EnvironmentVariables, and it recognises that the value is a `string`.
const myVariable = environmentVariables.myVariable;

// There's a getter defined for myBooleanVariable, so TypeScript
// recognises that it is a boolean.
const myBooleanVariable = environmentVariables.myBooleanVariable;

所以...这是可能的吗,还是我只是在浪费时间?

推荐答案

创建一种包装类来存储原始字符串,然后使用一些getter来执行转换,但我希望能够自动执行这一操作,而不是 for each 值都详尽地创建getter.

IIUC中,您有一个匹配环境配置的Variables类型的脚本,其中所有的值实际上都是字符串(通常是从dotenv得到的),并且所有的键都是UPPER_VONSE_CASE,并以"REACTION_APP_"为前缀.

现在,您希望某个实用程序将一些值转换为其正确的预期类型(通常为"true"字符串到true布尔值),并在可能的情况下将键转换为CamelCase(并删除前缀),同时最大限度地减少对此类实用程序的配置需求.

在这种情况下,正如您已经了解的那样,不幸的是,没有减少最少的配置,因为类型脚本类型在运行时不存在,但您确实需要在运行时(当您接收环境变量时)进行转换.

以类似于运行时验证用例的方式(例如,参见JoiYupZod等),您可以颠倒这种方法:首先编写配置作为环境变量 struct 的"真理源",然后从该配置推断出后者的类型.

能够将变量名称更改为驼峰大小写,并删除‘REACTION_APP’前缀[...]也许这是可以使用模板类型和泛型来完成的事情?

事实上,TypeScrip现在可以执行大约string manipulation on types个有限的任务.内置的实用程序不处理Snake_Case到CamelCase的转换(也不是相反的),但有几个可用的解决方案,例如在Is it possible to use mapped types in Typescript to change a types key names?

// Atomic type converters used for configuration
function envString(envValue: string) {
    return envValue;
}
function envBoolean(envValue: "true" | "false") {
    return envValue === "true";
}

// Example configuration
const config = {
    // Use camelCase
    myVariable: envString,
    myBooleanVariable: envBoolean,
}

// Example generating the converterFn
const myEnvConverter = envConverterFnBuilder(config);

const result = myEnvConverter({
    REACT_APP_MY_VARIABLE: 'Test',
    REACT_APP_MY_BOOLEAN_VARIABLE: 'true'
}); // Okay

result.myBooleanVariable;
//     ^? (property) myBooleanVariable: boolean

上面的envConverterFnBuilder ConverterFn构建器,它使用了一些帮助器类型,包括提到的CamelToSnake解决方案:

// ConverterFn builder
function envConverterFnBuilder<T extends { [Key in keyof T]: typeof envString | typeof envBoolean }>(envStructure: T) {
    return function envConverterFn(envDict: ConverterInput<T>): ConverterOutput<T> {
        const result = {} as any; // Will be populated below
        for (const configKey in envStructure) {
            // Convert camelCase key into UPPER_SNAKE_CASE with prefix, e.g. using Lodash
            const ENV_KEY = _.toUpper(_.snakeCase("reactApp_" + configKey)) as keyof ConverterInput<T>;

            const atomicConverter = envStructure[configKey];
            result[configKey] = atomicConverter(envDict[ENV_KEY] as any); // We know that the envValue matches the converter because they derive from the same key
        }
        return result;
    };
}

// Mapped types with key remapping using template literal type manipulation
type ConverterInput<T extends { [Key in keyof T]: typeof envString | typeof envBoolean }> = {
    [Key in keyof T as Uppercase<CamelToSnake<`reactApp_${string & Key}`>>]: Parameters<T[Key]>[0]
};
type ConverterOutput<T extends { [Key in keyof T]: typeof envString | typeof envBoolean }> = {
    [Key in keyof T]: ReturnType<T[Key]>
}

Playground Link

实时演示(判断运行时行为):https://playcode.io/1751585

Typescript相关问答推荐

如何用不同的键值初始化角react 形式

找不到带名称的管道''

类型脚本接口索引签名

在配置对象上映射时,类型脚本不正确地推断函数参数

rxjs forkJoin在错误后不会停止后续请求

类型脚本中没有接口的中间静态类

在分配给类型的只读变量中维护const的类型

参数属性类型定义的REACT-RUTER-DOM中的加载器属性错误

为什么Typescript不能推断类型?

通过泛型函数本身推断受约束的泛型参数的类型脚本问题

使用TypeScrip5强制使用方法参数类型的实验方法修饰符

为什么S struct 类型化(即鸭子类型化)需要非严格类型联合?

界面中数组中的混合类型导致打字错误

棱角分明-什么是...接口类型<;T>;扩展函数{new(...args:any[]):t;}...卑鄙?

为什么在这种情况下,打字脚本泛型无法正确自动完成?

创建一个函数,该函数返回一个行为方式与传入函数相同的函数

埃斯林特警告危险使用&as";

仅当类型为联合类型时映射属性类型

映射类型不是数组类型

在Typescript 无法正常工作的 Three.js 中更新动画中的 GLSL 统一变量