You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过TypeScript API提取无as const时JSX属性的窄化类型

(1) 开发目的

我正在尝试开发一款可记录文件内所有JSX属性及其对应窄化TypeScript类型的文档生成软件。

输入文件示例:

div标签的test属性被定义为仅接受值为hello的字符串字面量类型

const testAsConst = 'hello' as const;

const test = 'hello';

// 上述两个变量传入属性均可正常通过TS类型校验
<div test={test} />;

预期输出结果:[ [ 'test', '"hello"' ] ]

(2) 当前存在的问题

变量未添加as const修饰时,程序输出为[ [ 'test', 'string' ] ]
变量添加as const修饰时,程序输出为[ [ 'test', '"hello"' ] ],符合预期

我清楚as const的作用逻辑,但上述示例中未添加as const时TypeScript也不会抛出类型错误,说明此时test变量传入属性时的类型应当被窄化为"hello"而非宽泛的string类型,希望能实现无as const时也能提取到正确的窄化类型。

当前实现功能的软件源码

import typescript from 'typescript';

const program = typescript.createProgram(['inputFile.tsx'], { target: typescript.ScriptTarget.ESNext });

const typeChecker = program.getTypeChecker();

for (const sourceFile of program.getSourceFiles()) {
  if (!sourceFile.isDeclarationFile) {
    const output = visit(sourceFile);

    console.log(output);
  }
}

function visit(node: typescript.Node, output2: [string, string][] = []): [string, string][] | undefined {
  if (typescript.isJsxAttribute(node)) {
    const attributeName = node.name.text;

    const symbol = typeChecker.getSymbolAtLocation(node.name);

    if (symbol) {
      const type = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!);

      output2.push([attributeName, typeChecker.typeToString(type)]);
    }
  }

  typescript.forEachChild(node, node2 => {
    visit(node2, output2);
  });

  return output2;
}

问题根因

当前代码取类型的逻辑存在错误:现有写法获取的是JSX属性名对应属性定义处的声明类型,并非传入属性值在当前JSX上下文里的窄化类型。
按照TS的类型推断规则,未加as const的const test = 'hello'声明,变量本身的默认推断类型是拓宽(widened)后的string;只有当这个变量被传入要求更窄字面量类型的位置时,TS才会在传值位置做上下文类型窄化,校验是否匹配目标类型。原有写法完全没有获取传值位置的类型,自然只能拿到宽类型string。

修复方案

不要通过属性名的symbol拿声明类型,直接读取JSX属性的值节点,在当前JSX上下文位置调用类型检查器获取类型,就能拿到经过上下文窄化后的字面量类型。
首先修正Program创建配置,对齐TS常规校验行为:

const program = typescript.createProgram(['inputFile.tsx'], { 
  target: typescript.ScriptTarget.ESNext,
  strict: true,
  jsx: typescript.JsxEmit.React
});

再修改核心visit逻辑:

function visit(node: typescript.Node, output2: [string, string][] = []): [string, string][] | undefined {
  if (typescript.isJsxAttribute(node)) {
    const attributeName = node.name.text;
    if (!node.initializer) return;
    // 区分表达式传值和字面量传值场景,拿到实际的属性值节点
    const valueNode = typescript.isJsxExpression(node.initializer) 
      ? node.initializer.expression 
      : node.initializer;
    if (!valueNode) return;
    // 直接在值节点位置取类型,自动应用上下文窄化逻辑
    const type = typeChecker.getTypeAtLocation(valueNode);
    output2.push([attributeName, typeChecker.typeToString(type)]);
  }

  typescript.forEachChild(node, node2 => {
    visit(node2, output2);
  });

  return output2;
}

修改后,即使变量未加as const,只要传值位置能匹配目标字面量类型,就能正确拿到"hello"的窄化类型结果,和TS实际类型校验行为完全一致。


内容的提问来源于stack exchange,提问作者Altaula

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:09:20