【问题标题】:Typescript interface property to stringTypescript接口属性到字符串
【发布时间】:2015-04-13 08:02:22
【问题描述】:

问题/答案 - 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。所以我不知道如何使它更通用。

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

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


    basarat 答案是个好主意,但它不适用于接口。

    不能methodX(interfacePropertyToString(()=&gt;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) =&gt; {object.code;} 转换为多行函数。

    现在你可以随心所欲地使用它了:

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

    【讨论】:

    • 很好的答案!您能否解释一下 TypeScript 如何处理用户定义的接口以及将它们编译成什么?因为我没有找到太多关于这个的东西。谢谢!
    • 另外,你能想到用这种方式提取接口的所有属性的任何方法吗?谢谢!
    • @radu-matei TypeScript 接口不会转译为 Javascript。它根本不存在于 Javascript 中。
    【解决方案2】:

    您可以编写一个函数来解析函数的主体以查找名称,例如:

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

    其中getName 将在函数体上执行toString 以获取"function(){return something.name}" 形式的字符串,然后对其进行解析以获取"name"

    注意:然而,这取决于你如何缩小它。

    【讨论】:

      【解决方案3】:

      对于支持代理类的浏览器:

      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;
      

      【讨论】:

      • 这太棒了。 get 可能不应该采用空对象,而应该采用参数名称。代码质量工具可以将空解构标记为错误。 _ 可用于表示参数不重要。 get(_, prop)....
      【解决方案4】:

      我对 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));
      

      【讨论】:

        【解决方案5】:

        一些相关的问题 - 如何获取/设置属性路径的值。我为此写了两个类:

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

        【讨论】:

          【解决方案6】:

          如果您需要验证字符串,您可以基于keyofinterface 创建一个新的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;
          

          ILanguageStrings.ts

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

          zh-CN.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;
          

          【讨论】:

            猜你喜欢
            • 2018-12-13
            • 2018-07-14
            • 2020-09-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-01
            • 2019-04-25
            • 1970-01-01
            相关资源
            最近更新 更多