如何修复TypeScript中的“object is possibly undefined”错误及相关问题排查
Let's walk through your problem step by step—first understanding the TypeScript error, then why it targets the get call instead of has, and finally fixing the empty output you're seeing.
1. Why TypeScript flags this.ratesMap.get(rate[0]) as possibly undefined
TypeScript enforces strict null checks, and the Map.get() method is designed to return either the value associated with the key or undefined (if the key doesn't exist).
Even though you added a this.ratesMap.has(rate[0]) check, TypeScript's control flow analysis can't reliably track changes to ratesMap inside a forEach callback. Since forEach is a separate function, TypeScript can't guarantee that no other code modifies ratesMap between the has check and the get call. So it still treats the get result as potentially undefined.
2. Why TypeScript doesn't flag this.ratesMap.has(rate[0])
The Map.has() method returns a strict boolean (true/false)—there's no scenario where it would return undefined. TypeScript knows this, so it never throws an error for this line.
3. Fixing the error AND the empty output
Adding the ! non-null assertion silences TypeScript, but it doesn't fix the underlying issues causing your code to output nothing. Here's what you need to do:
a. Safely handle the Map.get() result (no assertions needed)
Instead of relying on !, cache the get result and let TypeScript's control flow analysis confirm it's not undefined:
Object.entries(rates).forEach((rate) => { const existingCard = this.ratesMap.get(rate[0]); if (existingCard) { existingCard.update(rate[1]); } else { const newCard = new CurrencyCard(rate[0], rate[1]); this.ratesMap.set(rate[0], newCard); } });
This way, TypeScript sees that existingCard is guaranteed to exist inside the if block, eliminating the error without risky assertions.
b. Fix the recentRates computed property
Your current computed property is incorrectly modifying this.cardsArray instead of just returning a value. MobX computed properties should be pure functions—they shouldn't mutate state. Adjust it to:
@computed public get recentRates(): CurrencyCard[] { return [...this.ratesMap.values()]; }
If you need cardsArray to stay in sync with ratesMap, update it directly inside the runInAction block after processing all rates:
runInAction(() => { Object.entries(rates).forEach((rate) => { // ... existing logic ... }); // Update cardsArray after modifying ratesMap this.cardsArray = [...this.ratesMap.values()]; });
c. Fix async initialization order
Your constructor calls this.getRates() (async) and immediately runs this.updateRate()—but getRates hasn't finished loading data yet. This means updateRate is likely operating on an empty ratesMap.
Fix this by moving initialization to an async method:
public constructor(@inject(CardApi) private api: CardApi) { makeObservable(this); this.init(); // Call async init method } private async init() { try { await this.getRates(); // Wait for rates to load first this.updateRate(); // Now runs with populated ratesMap } catch (err) { console.error('Failed to initialize rates:', err); } }
d. Validate API responses
Silent failures often happen when API responses aren't what you expect. Add checks to your loadRates method to catch issues early:
public async loadRates(): Promise<Record<string, number>> { const response = await fetch( `https://freecurrencyapi.net/api/v2/latest?apikey=MYAPIKEY&base_currency=USD` ); if (!response.ok) { throw new Error(`API request failed: ${response.statusText}`); } const data = await response.json(); if (!data?.data || typeof data.data !== 'object') { throw new Error('Invalid API response format'); } return data.data as Record<string, number>; }
This will log clear errors to the console if the API returns a failure or unexpected data, instead of silently proceeding with empty rates.
内容的提问来源于stack exchange,提问作者lusidespair

