能否用TypeScript从字符串字典生成强类型Environment对象?
可行,且有成熟实现方案
核心思路是结合 TypeScript 的映射类型、字面量类型推断,以及 JavaScript 的 Proxy 特性,既实现动态 getter 的自动生成,又让 TypeScript 完整识别动态属性的类型,同时支持自定义 getter 的类型推导。
步骤1:定义类型转换工具
先写两个关键工具类型,分别处理键名的驼峰转换和值的类型解析:
// 把 REACT_APP_XXX_YYY 格式的键转为驼峰命名 type SnakeToCamel<S extends string> = S extends `REACT_APP_${infer Rest}` ? Rest extends `${infer First}_${infer Tail}` ? `${Lowercase<First>}${SnakeToCamel<Tail>}` : Lowercase<Rest> : S; // 根据字符串字面量推断转换后的类型('true'→boolean、数字字符串→number,其余保留string) type ParseValue<T extends string> = T extends 'true' | 'false' ? boolean : T extends `${number}` ? number : T; // 生成最终的代理类型:映射原变量的键为驼峰,值为解析后的类型 type ProxiedVariables<Var extends Record<string, string>> = { [K in keyof Var as SnakeToCamel<K>]: ParseValue<Var[K]>; };
步骤2:用Proxy实现动态getter逻辑
通过 Proxy 拦截属性访问,自动处理前缀去除、驼峰转换和值类型转换,同时利用类型断言让 TypeScript 识别代理后的类型:
// 用as const保留字面量类型,确保类型推断精准 const variables = { REACT_APP_MY_VARIABLE: 'Test', REACT_APP_MY_BOOLEAN_VARIABLE: 'true', REACT_APP_MY_NUMBER_VARIABLE: '123' } as const; function createEnvProxy<Var extends Record<string, string>>(env: Var): ProxiedVariables<Var> { return new Proxy(env, { get(target, prop: string) { // 允许直接访问原始属性(如REACT_APP_MY_VARIABLE) if (prop in target) { return (target as any)[prop]; } // 将驼峰属性转回带前缀的蛇形键 const snakeProp = `REACT_APP_${prop.replace(/([A-Z])/g, '_$1').toUpperCase()}`; if (!(snakeProp in target)) { throw new Error(`环境变量 ${snakeProp} 不存在`); } // 执行值类型转换 const rawValue = target[snakeProp]; if (rawValue === 'true') return true; if (rawValue === 'false') return false; const num = Number(rawValue); if (!isNaN(num)) return num; return rawValue; } }) as ProxiedVariables<Var>; } // 使用代理对象,TypeScript会自动识别所有属性类型 const env = createEnvProxy(variables); console.log(env.myVariable); // 类型:string,值:'Test' console.log(env.myBooleanVariable); // 类型:boolean,值:true console.log(env.myNumberVariable); // 类型:number,值:123
步骤3:扩展自定义getter
如果需要为特定属性添加自定义转换逻辑,可通过交叉类型扩展代理类型,并在 Proxy 中加入自定义处理:
// 假设REACT_APP_MY_SPECIAL_VARIABLE的值是"100,foo",需要转为对象 type CustomProxiedVariables = ProxiedVariables<typeof variables> & { mySpecialVariable: { id: number; name: string }; }; function createEnvProxyWithCustom<Var extends Record<string, string>>(env: Var): CustomProxiedVariables { const baseProxy = createEnvProxy(env); return new Proxy(baseProxy, { get(target, prop: string) { if (prop === 'mySpecialVariable') { const raw = target.mySpecialVariable; const [id, name] = raw.split(','); return { id: Number(id), name }; } return (target as any)[prop]; } }) as CustomProxiedVariables; }
这种方案既避免了手动编写大量 getter,又让 TypeScript 完美识别所有动态属性和自定义 getter 的类型,完全满足需求。
内容的提问来源于stack exchange,提问作者8ytan
相关产品推荐
相关产品推荐

