Typescript:如何用全名填充对象属性

标签 typescript object properties

我有一个这样定义的变量:

interface MyObjects {
  Troll : {
    strength : string;
    dexterity : string;
    wisdom : string;
  },
  Child : {
    health : string;
    wellness : string;
  },
  Kitten : {
    sneakFactor : string;
    color : string;
  }
}

let myObjects : MyObjects;

有没有办法使用 TypeScript 填充 myObjects ,以便每个字符串都包含完全限定的名称,如下所示:

myObjects.Troll.strength = 'Troll_strength';
myObjects.Troll.dexterity= 'Troll_dexterity';
myObjects.Troll.wisdom = 'Troll_wisdom';
myObjects.Child.health = 'Child_health';
//etc

更新 1: 我希望使用类似 Object.keys( myObjects ) 的东西来获取所有键然后进行迭代,但我无法获取未初始化变量的键。


更新 2: 我通过使用更进一步:

declare function keys<T extends object>() : Array<keyof T>;
const k = keys<MyObjects>();

现在,我已将根目录下的所有键名称保存在 k 数组中。我可以像这样访问每个子对象:

myObjects[k]

...但我现在不确定如何获取 myObjects[k] 的所有子属性的数组,因为我没有为每个子属性定义类型。

最佳答案

这对于标准 TS 编译器来说是不可能的。 TypeScript 类型在运行时被完全删除,因此此信息不再可用。

但是,如果您使用自定义脚本而不是仅使用 tsc 来编译源代码,则这是可能的。

为了简化代码,我使用了 ts-simple-ast在这里。

这是个好主意吗?不……但是很有趣。

import { Project, VariableDeclaration, Type, WriterFunction } from 'ts-simple-ast' // ^21.0.0

const project = new Project()
project.addExistingSourceFiles('src/**/*.ts')

// Custom functionality
// Look for variables that have a doc comment with `@autoInit`
// Get their type, and assign values to https://stackoverflow.com/q/54260406/7186598
for (const sourceFile of project.getSourceFiles()) {
    // TODO: Class properties, object literal properties, etc.?
    const declarations = sourceFile.getVariableDeclarations()

    for (const declaration of declarations.filter(hasAutoInitTag)) {
        if (declaration.hasInitializer()) {
            console.warn(`'${declaration.getName()}' has an initializer and @autoInit tag. Skipping.`)
            continue
        }

        const type = declaration.getType()
        const writer = createWriterForType(declaration.getName(), type);

        const parentStatement = declaration.getParent().getParent()
        const index = sourceFile.getStatements().findIndex(statement => statement === parentStatement)

        // Insert after the variable declaration
        sourceFile.insertStatements(index + 1, writer);
    }

    console.log(sourceFile.getFullText())
    // Uncomment once you have verified it does what you want.
    // sourceFile.saveSync()
}

// There's almost certainly a better way to do this.
function hasAutoInitTag(declaration: VariableDeclaration) {
    // Comments are attached to a VariableDeclarationList which contains VariableDeclarations, so
    // get the parent.
    const comments = declaration.getParent().getLeadingCommentRanges().map(range => range.getText())
    return comments.some(comment => comment.includes('@autoInit'))
}

function createWriterForType(name: string, type: Type): WriterFunction {
    return writer => {
        function writeTypeInitializer(nameStack: string[], type: Type) {
            if (type.isString()) {
                // Some logic for non-standard names is probably a good idea here.
                // this won't handle names like '()\'"'
                writer.writeLine(`${nameStack.join('.')} = '${nameStack.slice(1).join('_')}'`)
            } else if (type.isObject()) {
                writer.writeLine(`${nameStack.join('.')} = {}`)
                for (const prop of type.getProperties()) {
                    const node = prop.getValueDeclarationOrThrow()
                    writeTypeInitializer(nameStack.concat(prop.getName()), prop.getTypeAtLocation(node))
                }
            } else {
                console.warn('Unknown type', nameStack, type.getText())
            }
        }

        writeTypeInitializer([name], type)
    }
}

现在是不太令人兴奋的解决方案。

您可以从对象生成接口(interface),而不是使用接口(interface)来描述对象。然后可以通过 autoInit 函数访问这些键,该函数可以生成您想要的字符串。 Playground demo

// Exactly the same as the original interface
type MyObjects = typeof myObjects

let myObjects = {
    Troll: { 
        strength: '',
        dexterity: '',
        wisdom: ''
    },
    Child: {
        health: '',
        wellness: ''
    },
    Kitten: {
        sneakFactor: '',
        color: ''
    }
};

autoInit(myObjects)
console.log(myObjects)

type AutoInitable = { [k: string]: AutoInitable } | string
function autoInit(obj: { [k: string]: AutoInitable }, nameStack: string[] = []) {
    for (const key of Object.keys(obj)) {
        const val = obj[key]
        if (typeof val === 'string') {
            obj[key] = [...nameStack, key].join('_')
        } else {
            autoInit(val, nameStack.concat(key))
        }
    }
}

链接的问题adiga是另一个选项,这可能更好,因为它更通用。您可以使用 MyObjects['Troll'] 访问子类型,但我很确定您无法像使用上述两个选项那样自动在任何深度执行此操作。

关于Typescript:如何用全名填充对象属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54260406/

相关文章:

javascript - 无效的配置对象 output.path 不是绝对路径

javascript - node.js 从字符串属性名称设置属性

c# - 编译时由设计器重置属性

c# - 已编译的 lambda 表达式用作属性 getter 和 setter : wrong benchmarking method or wrong lambda expression construction?

javascript - typescript 问题 - 澄清几点

javascript - 当我创建类型化对象数组时,为什么没有调用我的自定义 setter?

javascript - 如何在 HTML 中查看数组

javascript - 从对象日期数组中分离出月份,并根据日期对应的月份进行分类

wpf - LargeChange 和 SmallChange 属性对 ProgressBar 控件有何作用?

javascript - 如何使用 Visual studio 2019 Typescript 使 React 对象不再需要任何声明