For one, it signals different intent, which tends to be important in larger projects with more collaborators. The more you can make intentions obvious, the easier it'll be for others to understand and contribute.
A value whose type we don't know is different from a value which we know could be any type.
Consider a ‘dump’ function. By design, it handles anything you throw at it - strings, objects, booleans, etc. It won’t change. That’s ‘Any’.
Whereas ‘Unknown’ might be a stepping stone to introduce a more specific type once all the use cases are known. I let it handle strings and integers, log a line about any other type that calls it, and continue behaviour as it is today.
The difference comes when you interface with other well-typed code and want to convert from any/unknown down to a specific type.
For example:
function handleString(s: string) {
//...
}
declare const a: any;
declare const u: unknown;
handleString(a); // works with zero intervention
handleString(u); // error: Argument of type 'unknown' is not assignable to parameter of type 'string'
handleString(u as string); // works again with explicit re-typing
A dump function accepting "unknown" is signalling "I'm not sure what this function could handle, probably a lot of different types" vs accepting "any" where it's telling you "I can accept any type you throw at me".
They're the same as much as "1 + 1 = 2" and "2 + 0 = 2" is the same. The result might be the same ("any" and "unknown" accepting any type) but again, the intent is different (a value we know can be anything vs a value we don't know what type it'll have).
A value whose type we don't know is different from a value which we know could be any type.