簡體   English   中英

打字稿接口屬性到字符串

[英]Typescript interface property to string

問題/答案 - 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 的地方

但這很糟糕:如果我重命名一個屬性(假設我想將namenamelastname ),我將不得不手動更新我的所有代碼。

我不想要這種依賴。

由於打字稿接口沒有 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。 所以我不知道如何使它更通用。

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

您可以編寫一個函數來解析函數體以查找名稱,例如:

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

其中getName將在函數體上執行toString以獲取形式為"function(){return something.name}"的字符串,然后對其進行解析以獲取"name"

注意:然而,這有一種破壞的趨勢,這取決於你如何縮小它。

對於支持 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;

我稍微改變了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));

有點相關的問題 - 如何獲取/設置屬性路徑的值。 我為此編寫了兩個類:

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

如果您需要驗證字符串,您可以根據interface keyof創建一個新type 如果你有一個對象,你可以使用keyof typeof對象。

語言文件示例:

本地化服務.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

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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM