[英]Typescript interface property to string
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 会自动告诉您存在错误。
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.但这很糟糕:如果我重命名一个属性(假设我想将
name
重name
为lastname
),我将不得不手动更新我的所有代码。
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.