Typescript接口属性转换为字符串

25

问题/答案 - 2021更新

这个问题是6年前提出的,当时我对TypeScript的理解非常有限! 我不想删除它,因为仍然有一些人在阅读这篇文章。

如果你想让一个变量的类型成为另一个变量的属性,你可以使用keyof

例如:

interface User {
    name: string;
    age: number;
}

const nameProperty: keyof User = 'name'; // ok
const ageProperty: keyof User = 'age'; // ok
const emailProperty: keyof User = 'email'; // not ok

如果你想要一个接受另一个参数的属性作为参数的方法,可以使用泛型将两种类型链接在一起。

示例,使用泛型和 keyof

const foo = <TObject extends object>(
    object: TObject,
    property: keyof TObject
) => {
    // You can use object[property] here
};

foo({ a: 1, b: 2 }, 'a'); // ok
foo({ a: 1, b: 2 }, 'b'); // ok
foo({ a: 1, b: 2 }, 'c'); // not ok

使用泛型+Record的示例:

const foo = <TKey extends string>(
    object: Record<TKey, unknown>,
    property: TKey
) => {
    // You can use object[property] here
};

foo({ a: 1, b: 2 }, 'a'); // ok
foo({ a: 1, b: 2 }, 'b'); // ok
foo({ a: 1, b: 2 }, 'c'); // not ok

请勿使用此问题答案!如果您在某个时刻重命名属性,TypeScript将自动告诉您存在错误。


原始问题(2014年)

目标

我有一个 TypeScript 接口:

interface IInterface{
    id: number;
    name: string;
}

我有一些方法需要输入属性名称(字符串)

例如:

var methodX = ( property: string, object: any ) => {
    // use object[property]
};

我的问题是,当我调用methodX时,我必须以字符串的形式写入属性名称。

例如:methodX("name", objectX);其中objectX实现了IInterface接口

但这是不好的:如果我重命名属性(假设我想将name重命名为lastname),则必须手动更新所有代码。

而我不想有这种依赖性。

由于TypeScript接口没有JS实现,因此我不知道如何不使用字符串。

我希望有一个像这样的解决方案:methodX(IInterface.name.propertytoString(), objectX);

我很新于JS,你能看到其他替代方案吗?

(可选)更多细节:为什么需要将属性作为参数传递,而不使用通用方法?

我使用链接数据的方法:

linkData = <TA, TB>(
    inputList: TA[],
    inputId: string,
    inputPlace: string,
    outputList: TB[],
    outputId: string ) => {

    var mapDestinationItemId: any = {};
    var i: number;
    for ( i = 0; i < outputList.length; ++i ) {
        mapDestinationItemId[outputList[i][outputId]] = outputList[i];
    }

    var itemDestination, itemSource;
    for ( i = 0; i < inputList.length; ++i ) {
        itemDestination = inputList[i];
        itemSource = mapDestinationItemId[itemDestination[inputId]];
        if ( itemSource ) {
            itemDestination[inputPlace] = itemSource;
        }
    }
};

但是TA和TB可以有很多不同的id,所以我不知道如何使它更加通用。

6个回答

15

更新2019年:这个答案已经过时,请查看直接添加到问题中的更新。


basarat 的答案是一个好主意,但在使用接口时无法正常工作。

无法编写 methodX(interfacePropertyToString(()=>interfaceX.porpertyname), objectX) ,因为 interfaceX 不是一个对象。

接口是抽象的,只用于 TypeScript,它们不存在于 Javascript 中。

但感谢他的答案,我找到了解决方案: 在方法中使用参数

最后我们有:

    interfacePropertyToString = ( property: (object: any) => void ) => {
        var chaine = property.toString();
        var arr = chaine.match( /[\s\S]*{[\s\S]*\.([^\.; ]*)[ ;\n]*}/ );
        return arr[1];
    };

我们必须使用[\s\S]来匹配多行,因为TypeScript将(object: Interface) => {object.code;}转换为多行函数。
现在您可以根据需要使用它:
        interfacePropertyToString(( o: Interface ) => { o.interfaceProperty});
        interfacePropertyToString( function ( o: Interface  ) { o.interfaceProperty});

很棒的回答!您能否稍微解释一下TypeScript如何处理用户定义的接口以及它将其编译成什么?因为我没有找到太多相关信息。谢谢! - radu-matei
另外,你能想到任何一种方法以这种方式提取接口的所有属性吗?谢谢! - radu-matei
@radu-matei TypeScript接口不会转译成Javascript。在Javascript中,它根本不存在。 - Machtyn
出现以下错误: 无法读取 null 的属性(读取 '1') - KJ Sudarshan
不确定你们从哪里得到那个正则表达式,但基于代码并假设格式如此:o => o.SomeProperty,你需要像这样更新正则表达式 [a-zA-Z]+\s=>\s[a-zA-Z]+\.([A-Za-z0-9]+) 然后使用 return arr[2]。该正则表达式基本上将属性名称放在一个组中,以便我们可以从 arr 中提取它。 - The Muffin Man

3

我稍微修改了 basarat 的代码,使其可以作为通用代码使用:

const P = <T>( property: (object: T) => void ) => {
    const chaine = property.toString();
    const arr = chaine.match( /[\s\S]*{[\s\S]*\.([^\.; ]*)[ ;\n]*}/ );
    return arr[1];
};

一个使用示例:

console.log(P<MyInterface>(p => p.propertyName));

这应该是被选中的答案! - JAN

3

对于支持 Proxy 类的浏览器:

function propToString<T>(obj?: T): T {
  return new Proxy({}, {
    get({}, prop) {
      return prop;
    }
  }) as T;
}

class Foo {
  bar: string;
  fooBar: string;
}

console.log(propToString<Foo>().bar, propToString(new Foo()).fooBar);
// Prints: bar fooBar

// Cache the values for improved performance:
const Foo_bar = propToString<Foo>().bar;

1
这很棒。get 可能不应该接受一个空对象,而应该接受一个参数名称。代码质量工具可以将空解构标记为错误。_ 可以用来表示参数不重要。get(_, prop).... - Ryan Shohoney

2
你可以编写一个函数来解析函数体以查找名称,例如:
methodX(getName(()=>something.name), objectX)

getName方法将会对函数体执行toString,得到一个形如"function(){return something.name}"的字符串,再解析它以获取"name"

注意:这种方法可能因为压缩方式的不同而出现错误。


0
有一个相关的问题 - 如何获取/设置属性路径的值。我写了两个类来解决这个问题:
export class PropertyPath {
    static paths = new Map<string, PropertyPath>()

    static get<T, P>(lambda: (prop:T) => P) : PropertyPath {
        const funcBody = lambda.toString();
        var ret : PropertyPath = this.paths[funcBody];
        if (!ret) {
            const matches = funcBody.match( /(?:return[\s]+)(?:\w+\.)((?:\.?\w+)+)/ ); //first prop ignores
            var path = matches[1];
            ret = new PropertyPath(path.split("."));
            this.paths[funcBody] = ret;
        }
        return ret;
    };

    path : Array<string>

    constructor(path : Array<string>) {
        this.path = path
    }

    getValue( context : any) {
        const me = this;
        var v : any;
        return this.path.reduce( (previous, current, i, path) => {
            try {
                return previous[current];
            }
            catch (e) {
                throw {
                    message : `Error getting value by path. Path: '${path.join(".")}'. Token: '${current}'(${i})`,
                    innerException: e
                };
            }
        }, context)
    }

    setValue( context : any, value : any) {
        const me = this;
        var v : any;
        this.path.reduce( (previous, current, i, path) => {
            try {
                if (i == path.length - 1) {
                    previous[current] = value
                }
                return previous[current];
            }
            catch (e) {
                throw {
                    message : `Error setting value by path. Path: '${path.join(".")}'. Token: '${current}'(${i}). Value: ${value}`,
                    innerException: e
                };
            }
        }, context)
    }

}

使用示例:

var p = PropertyPath.get((data:Data) => data.person.middleName)
var v = p.getValue(data)
p.setValue(data, newValue)

加点糖吧:

export class PropertyPathContexted {

    static get<T, P>(obj : T, lambda: (prop:T) => P) : PropertyPathContexted {
        return new PropertyPathContexted(obj, PropertyPath.get(lambda));
    };

    context: any
    propertyPath: PropertyPath

    constructor(context: any, propertyPath: PropertyPath) {
        this.context = context
        this.propertyPath = propertyPath
    }

    getValue = () => this.propertyPath.getValue(this.context)

    setValue = ( value : any) => {this.propertyPath.setValue(this.context, value) }

}

使用方法:

var p = PropertyPathContexted.get(data, () => data.person.middleName)
var v = p.getValue()
p.setValue("lala")

我发现在React中双向数据绑定最新的方式非常方便:

var valueLink = function<T, P>( context: T, lambda: (prop:T) => P) {
    var p = PropertyPathContexted.get(context, lambda);
    return {
        value: p.getValue(),
        requestChange: (newValue) => {
            p.setValue(newValue);
        }
    }
};

render() {
   var data = getSomeData()
   //...
   return (
       //...
       <input name='person.surnames' placeholder='Surnames' valueLink={valueLink(data, () => data.person.surnames)}/>
       //...
   )
}

0
如果您需要验证字符串,可以基于接口的keyof创建一个新的type。如果您有一个对象,可以使用keyof typeof对象。
语言文件示例:

localizationService.ts

import svSE from './languages/sv-SE';
import enUS from './languages/en-US';
import arSA from './languages/ar-SA';
import { ILanguageStrings } from './ILanguageStrings';

/*
If more languages are added this could be changed to:
    "sv-SE": svSE,
    "en-US": enUS,
    "ar-SA": arSA
*/

export const messages = {
    "sv": svSE,
    "en": enUS,
    "ar": arSA
};

//Identical types
export type IntlMessageID = keyof typeof messages.en;
export type IntlMessageID2 = keyof ILanguageStrings;

enter image description here

ILanguageStrings.ts

export interface ILanguageStrings {
    appName: string
    narration: string
    language: string
    "app.example-with-special-charactes": string
}

en-US.ts

import { ILanguageStrings } from '../ILanguageStrings';

const language: ILanguageStrings = {
    appName: "App Eng",
    narration: "Narration",
    language: "Language",
    "app.example-with-special-charactes": "Learn React."
}

export default language;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接