🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 kürzlich 10 Min Lesezeit
0

React displayName Without Strings: A C#-Inspired TypeScript Proxy Trick

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

It started with a very small problem.



In our React component library, we wanted every component to have a nice displayName.



So we did the obvious thing:




CODE
Component.displayName = "Component";






Simple. Clear. Boring.



And then reality arrived.



From time to time, developers renamed components. Maybe Button became IconButton. Maybe UserCard became CustomerCard.



But the displayName string?



That stayed behind.




CODE
CustomerCard.displayName = "UserCard";






Technically everything still worked.



But debugging became slightly cursed.



React DevTools showed outdated names, logs became misleading, and the code looked correct enough that nobody noticed immediately.



So I started thinking:




How can we force the name to stay in sync with the actual symbol?




Because the real problem was not displayName.



The real problem was this:




CODE
"Component"






A string literal does not participate in refactoring.



Your IDE can rename the function. TypeScript can check the types. But that string just sits there, smiling suspiciously.



So the question became more general:




What are the possible ways to get the name of a symbol in a reliable manner?







First attempt: keyof



The first simple helper looked like this:




CODE
export function publicNameOf<T_Type>(key: keyof T_Type): typeof key
{
return key;
}






And it kind of works.



For example:




CODE
type User =
{
name: string;
age: number;
};

const name = publicNameOf<User>("name");






At least now TypeScript checks that "name" is actually a key of User.



If I write this:




CODE
const name = publicNameOf<User>("firstName");






TypeScript complains.



That is already better than a completely random string.



But it still has a few problems.



First, it only works with direct members.




CODE
publicNameOf<User>("profile");






That is fine, but it does not naturally express something nested like:




CODE
user.profile.avatar.url






Second, it still requires a string literal.



So if the actual member is renamed, your IDE may not update this:




CODE
publicNameOf<User>("oldName");






TypeScript may catch it later, depending on the type. But it is still not the same as pointing at the member using normal language syntax.



Third, keyof is still a type-level list of accessible keys.



That matters especially for classes.



For example:




CODE
class UserService
{
public start()
{
}

private stop()
{
}
}

type UserServiceKey = keyof UserService;






UserServiceKey contains "start", but it does not contain "stop".



And that makes sense. From the outside, stop is not part of the public shape of UserService.



But there is an important nuance here.



Inside the class, TypeScript does allow you to access private members. The language already knows whether a particular member access is legal depending on where that access is written.



So sometimes the problem is not:




Give me all public keys of this type.




Sometimes the problem is:




Let me point at this member using the same syntax I would normally use in code.




That is a different goal.



And keyof is not quite that.






What I actually wanted



What I wanted was something closer to C# expression trees.



In C#, you can write something like:




CODE
Expression<Func<User, object>> expression = user => user.Profile.Avatar.Url;






And then inspect the expression to understand which member path was used.



I wanted a TypeScript version of that idea.



Something like:




CODE
($) => $.Component;






Where $ is some object that has the member I want to point at.



The important part is this:




CODE
$.Component






That is not a string.



That is a real property access expression.



So rename refactoring has a chance to follow it.



Of course, TypeScript does not have C# expression trees.



But JavaScript has Proxy.



And sometimes that is enough.






The proxy trick



The idea is surprisingly simple.



We create a fake object.



Every time someone accesses a property on it, we record that property name.



Then we return the same fake object again, so the selector can continue walking deeper.




CODE
export type RuntimeSelector<T_Target, T_Member = any> =
(target: T_Target) => T_Member;

export function runtimePathOf
<
T_Target,
T_Member = any
>
(
selector: RuntimeSelector<T_Target, T_Member>
)
: string[]
{
const path: string[] = [];

const pathProxy: string[] = new Proxy
(
path,
{
get: (target, key) =>
{
target.push(key.toString());

return pathProxy;
}
}
);

selector(pathProxy as T_Target);

return path;
}






Now we can do this:




CODE
const path = runtimePathOf<User>
(
user => user.profile.avatar.url
);






And the result is:




CODE
["profile", "avatar", "url"]






No AST parsing.



No decorators.



No compiler plugin.



Just a runtime proxy pretending to be your object.






Getting only the final name



For the original displayName problem, I did not need the full path.



I only needed the last segment.



So I added a tiny wrapper:




CODE
export function runtimeNameOf
<
T_Target,
T_Member = any
>
(
selector: RuntimeSelector<T_Target, T_Member>
)
: string
{
const path = runtimePathOf(selector);

return path[path.length - 1] ?? "";
}






Now this:




CODE
const name = runtimeNameOf<User>
(
user => user.profile.avatar.url
);






Returns:




CODE
"url"






And this:




CODE
const name = runtimeNameOf<$>($ => $.Component);






Returns:




CODE
"Component"









What about private members?



This is also where the earlier keyof limitation becomes interesting.



The proxy helper does not bypass TypeScript privacy rules.



It still follows normal TypeScript access rules at the place where the selector is written.



So outside a class, this would still be illegal if stop is private.



But inside the class, where TypeScript allows private access, the selector can point at the private member using normal syntax:




CODE
class UserService
{
private stop()
{
}

public getPrivateMethodName()
{
return runtimeNameOf<UserService>(service => service.stop);
}
}






That is something keyof UserService does not give you.



keyof UserService describes the public instance shape.



The selector approach is different: it lets you write a member access expression and lets TypeScript decide whether that access is valid from that location.



So this is not a way to break privacy.



It is a way to follow the language rules instead of asking for a public list of keys.






Back to the component



So now we have the general tool:




CODE
runtimeNameOf<$>($ => $.Component);






The next question is:




What should $ actually be?




We need $ to be some type that contains Component.



Where does Component live?



In my case, it is exported from the current module.



And TypeScript can get the type of a module very easily:




CODE
typeof import(".")






That gives us the type shape of the current module exports.



So the final version becomes:




CODE
Component.displayName = runtimeNameOf<typeof import(".")>
(
$ => $.Component
);






Now Component is referenced through a real property access expression.



If the exported component is renamed, the selector can be updated by normal refactoring tools.



Instead of this fragile version:




CODE
Component.displayName = "Component";






We now have this:




CODE
Component.displayName = runtimeNameOf<typeof import(".")>
(
$ => $.Component
);






The string is no longer manually written.



It is derived from the property access.



That was the whole point.






What this is not



This is not real reflection.



It does not inspect TypeScript types at runtime.



It does not know anything magical about your source code.



It simply runs a function against a proxy and records which properties were accessed.



So this works:




CODE
runtimePathOf<User>
(
user => user.profile.avatar.url
);






But this is not the kind of thing you should do:




CODE
runtimePathOf<User>
(
user => user.getProfile().avatar.url
);






Because now you are calling a function on a proxy.



Our proxy only handles property access. It does not behave like a real User.



This helper is meant only for simple property selectors.






The bundler question



There is one important bundler caveat.



The TypeScript type argument is erased:




CODE
runtimeNameOf<typeof import(".")>
(
$ => $.Component
);






becomes ordinary JavaScript:




CODE
runtimeNameOf($ => $.Component);






So the bundler is not protecting this because it understands typeof import(".").



It simply sees a lambda with a property access.



Normal minification may rename the local parameter:




CODE
$ => $.Component






into something like:




CODE
n => n.Component






That is fine. The proxy still sees the property key "Component".



The dangerous case is property mangling:




CODE
n => n.Component






could become:




CODE
n => n.a






Then the proxy would record "a" instead of "Component".



Most common bundler setups do not mangle arbitrary property names by default. They usually mangle local variables and function names, not public-looking property accesses.



So this approach is not protected by TypeScript metadata at runtime.



It is mostly safe because normal bundlers do not rewrite property keys like .Component unless property mangling is explicitly enabled.



So the practical rule is:




This technique avoids function-name minification problems. It is safe for normal public exports in typical bundler setups, but you should still check your production bundle if you use aggressive mangling.







Final result



So the final API is very small:




CODE
export type RuntimeSelector<T_Target, T_Member = any> =
(target: T_Target) => T_Member;

export function runtimePathOf
<
T_Target,
T_Member = any
>
(
selector: RuntimeSelector<T_Target, T_Member>
)
: string[]
{
const path: string[] = [];

const pathProxy: string[] = new Proxy
(
path,
{
get: (target, key) =>
{
target.push(key.toString());

return pathProxy;
}
}
);

selector(pathProxy as T_Target);

return path;
}

export function runtimeNameOf
<
T_Target,
T_Member = any
>
(
selector: RuntimeSelector<T_Target, T_Member>
)
: string
{
const path = runtimePathOf(selector);

return path[path.length - 1] ?? "";
}






And the usage is:




CODE
Component.displayName = runtimeNameOf<typeof import(".")>
(
$ => $.Component
);






A tiny helper.



A tiny proxy.



And one less stale string literal in the codebase.






Where this idea could go next



This tiny helper only records property access, but the general idea can grow into something much bigger.



A good mental model here is Entity Framework Core in C#.



EF Core can take expression trees, inspect what you wrote, and turn that structure into SQL queries. That is not what this helper does, and JavaScript proxies are not the same thing as C# expression trees.



But the direction is similar: developer-friendly syntax on the outside, metadata on the inside.



Once the object passed into the selector is fake, it does not have to behave exactly like the real object.



It can expose an extended API.



For example, a query builder could return special expression objects from property access and attach comparison helpers to them:




CODE
query<User>
(
user => user.age.$gt(18)
);






Or it could use symbols to avoid colliding with real domain members:




CODE
query<User>
(
user => user.age[Query.greaterThan](18)
);






At that point, user.age is not really a number at runtime. It is an expression node that represents the age field. Calling $gt(18) or [Query.greaterThan](18) can record a comparison and turn it into query metadata.



That is a different API design from runtimeNameOf, but it comes from the same core idea:




Give developers a typed object-shaped surface, run their selector against controlled proxy objects, and convert the interaction into metadata.




So no, this is not full C# expression trees, and it is not EF Core for TypeScript out of the box. JavaScript will not let a proxy directly intercept operators like >, ===, or &&.



But with an extended API, helpers, or symbol-based methods, this pattern could become the foundation for something much more query-like.






Conclusion



This is one of those TypeScript tricks that is not exactly "real reflection", but still solves a very real problem.



We wanted component display names to stay in sync with actual component symbols.



A simple string was too fragile.



keyof helped a little, but still required manually passing a string and only described the public key shape.



A proxy-based selector gave us a nicer middle ground:




CODE
$ => $.Component






It is still runtime JavaScript.



It is still small and understandable.



But now the important part is written as a real property access, so TypeScript and refactoring tools can help us.



And honestly, that is good enough for me.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten React displayName Without Strings: A C#-Inspired TypeScript Proxy Trick

Thematisch verwandte Begriffe: React, displayName, Without, Strings · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...