改善
Kaizen  · Today I Learned by Ville Säävuori

Narrowing unknown type in TypeScript

Type narrowing is one of the most useful and powerful features of TypeScript. Sometimes, however, it can be a bit too complicated.

Consider this simple function that uses Firebase:

export const useSignOut = async () => {
  try {
    const auth = getAuth()
    await signOut(auth)
  } catch (err: any) {
    alert(err.message)
  }
}

If you want to type this properly without any, you’d probably want to do something in the lines of if (err.message !== undefined) but that fails with an error Object is of type 'unknown'. It’s hard to do this “correctly” and there are a couple of open issues to make this easier. But luckily, as almost always, there’s also a Stack Overflow question with an answer that works and is easy to implement.

Add these helpers to your project:

function hasKey<K extends string, T extends object>(obj: T, key: K): obj is T & Record<K, unknown> {
  return key in obj
}

function isObjectWithKey<T, K extends string>(
  obj: T,
  key: K
): obj is T & object & Record<K, unknown> {
  return typeof obj === 'object' && obj !== null && key in obj
}

and then refactor the original if to:

if (isObjectWithKey(err, 'message')) {
  alert(err.message)
}

Both hasKey and isObjectWithKey are very useful to have in your everyday TS toolbelt. And speaking of toolbelts, this being the JavaScript world, there obviously is already a package for it; ts-toolbelt has a ton of these kind of helpers, battle-tested and ready to use!