There is nothing wrong with the above approach, however generally I try to steer people clear from using createType
(all string name based) unless there is no other way. Since the API always converts types anyway, the use should be minimal in "normal" code.
For the above, the api does indeed provide a shortcut for quick construction (I'm using your naming here for alignment) -
// re-construct an extrinsic from the input
const y = api.tx(h);
// log, submit, etc.
console.log(y.toHuman());
Although you are excluding historic, I'll just add some detail for that usecase for future searchers. As you alluded to the both your and my construction does require the same runtime/metadata, the latter being quite important since call indexes may not have moved between upgrades, but generally type indexes (as provided since metadata v14) do move around on upgrades.
I took a quick look at the historic api.at(<blockHash>)
and the interface returned there does not contain an .tx(<hex-goes-here>)
helper, so the only way there to reconstruct is indeed via apiAt.createType(...)
as above. (This seems like an oversight, will log it)