简体   繁体   English

打字稿接口属性到字符串

[英]Typescript interface property to string

Question/Answer - Update 2021问题/答案 - 2021 年更新

This questions was asked 6 years ago , and I had very little understanding of Typescript!这个问题是6年前问的,我对Typescript了解很少! I don't want to remove it because there are still some people reading this post.我不想删除它,因为还有人在阅读这篇文章。

If you want the type of a variable to be a property of another one, you can use keyof .如果您希望变量的类型成为另一个的属性,则可以使用keyof

Example:例子:

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

If you want a method that takes a parameter which is a property of another parameter you can use generics to link both types together.如果你想要一个方法接受一个参数,它是另一个参数的属性,你可以使用泛型将两种类型链接在一起。

Example using generics + keyof :使用泛型 + 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

Example using generics + Record :使用泛型 + 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

Don't use this question answers please!请不要使用此问题的答案! Typescript will automatically tell you that there is an error if you rename the property at some point.如果您在某个时候重命名属性,Typescript 会自动告诉您存在错误。


Original question (2014)原始问题 (2014)

Objective客观的

I have an interface TypeScript :我有一个接口 TypeScript :

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

I have some methods which take in entry the name of a property (string).我有一些方法可以输入属性的名称(字符串)。

Ex :例如

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

My problem is that when i call methodX , I have to write the property name in string.我的问题是,当我调用methodX ,我必须在字符串中写入属性名称。

Ex : methodX("name", objectX);例如: methodX("name", objectX); where objectX implements IInterface objectX 实现 IInterface 的地方

But this is BAD : If i rename a property (let's say i want to rename name to lastname ) i will have to update manually all my code.但这很糟糕:如果我重命名一个属性(假设我想将namenamelastname ),我将不得不手动更新我的所有代码。

And I don't want this dependency.我不想要这种依赖。

As typescript interfaces have no JS implementations, I don't see how I could not use string.由于打字稿接口没有 JS 实现,我不知道我怎么不能使用字符串。

I want to have something like : methodX(IInterface.name.propertytoString(), objectX);我想要类似的东西: methodX(IInterface.name.propertytoString(), objectX);

I'm pretty new to JS, do you see an alternative ?我对 JS 很陌生,你有没有其他选择?

(Optional) More details : Why do I need to pass properties as parameter, and why I don't use a generic method ? (可选)更多细节:为什么我需要将属性作为参数传递,为什么我不使用泛型方法?

I use methods that link data :我使用链接数据的方法:

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;
        }
    }
};

But TA and TB can have a lot of different ids.但是 TA 和 TB 可以有很多不同的 ID。 So i don't see how to make it more generic.所以我不知道如何使它更通用。

Update 2019: This answer is outdated, please look at the update added directly into the question. 2019 年更新:此答案已过时,请查看直接添加到问题中的更新。


basarat answer is a good idea, but it doesn't work with interfaces. basarat答案是个好主意,但它不适用于接口。

You can't write methodX(interfacePropertyToString(()=>interfaceX.porpertyname), objectX) because interfaceX is not an object.不能methodX(interfacePropertyToString(()=>interfaceX.porpertyname), objectX)因为interfaceX不是一个对象。

Interfaces are abstractions and they are used only for TypeScript, they doesn't exist in Javascript.接口是抽象的,它们仅用于 TypeScript,它们不存在于 Javascript 中。

But thanks to his answer i found out the solution : using a parameter in the method .但是由于他的回答,我找到了解决方案:在方法中使用参数

Finally we have :最后我们有:

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

We have to use [\\s\\S] to be able to match on multilines because Typescript convert (object: Interface) => {object.code;} to a multiline function.我们必须使用[\\s\\S]才能匹配多行,因为 Typescript 将(object: Interface) => {object.code;}转换为多行函数。

Now you can use it as you want :现在你可以随意使用它:

        interfacePropertyToString(( o: Interface ) => { o.interfaceProperty});
        interfacePropertyToString( function ( o: Interface  ) { o.interfaceProperty});

You could write a function to parse the body of a function to find the name eg:您可以编写一个函数来解析函数体以查找名称,例如:

methodX(getName(()=>something.name), objectX)

Where getName will do a toString on the function body to get a string of the form "function(){return something.name}" and then parse it to get "name" .其中getName将在函数体上执行toString以获取形式为"function(){return something.name}"的字符串,然后对其进行解析以获取"name"

Note: however this has a tendency to break depending upon how you minify it.注意:然而,这有一种破坏的趋势,这取决于你如何缩小它。

For browsers that support the Proxy class:对于支持 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;

I've changed basarat code a little bit, so we can use it as generic:我稍微改变了basarat代码,因此我们可以将其用作通用代码:

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

And example usage:和示例用法:

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

Somewhat related problem - how to get/set a value to a property path.有点相关的问题 - 如何获取/设置属性路径的值。 I wrote two classes for that:我为此编写了两个类:

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)
    }

}

Example of usage:用法示例:

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

Some sugar over it:加点糖:

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) }

}

And usage:和用法:

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

I find the the latest quite convenient in two-way databinding in React:我发现 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)}/>
       //...
   )
}

If you need to validate the strings you can create a new type based on keyof from the interface .如果您需要验证字符串,您可以根据interface keyof创建一个新type If you have an object you can use keyof typeof object.如果你有一个对象,你可以使用keyof typeof对象。

Example for language files:语言文件示例:

localizationService.ts本地化服务.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;

在此处输入图片说明

ILanguageStrings.ts ILanguageStrings.ts

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

en-US.ts 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM