When the compiler cannot deduce the types of the from
, to
, and coords
variables extracted from the function parameter, it defaults to inferring the the any
type, which is inherently risky and allows for unrestricted operations without further error detection. In scenarios where you have enabled the --strict
compiler features or at least the --noImplicitAny
flag, this defaulting to any
will trigger a warning to alert you about the loss of type safety.
The recommended approach is to specify the type for the function parameter to explicitly inform the compiler about its expected form. If you still wish to utilize any
and bypass the warning, you can do so by clearly indicating it:
export const adjustElements = ({ from, to, coords }: any) => {
let to_rect = to.getBoundingCleintRect(),
width = to_rect.width * ((coords[0] - 1) * 0.5),
height = to_rect.heigth * ((coords[1] - 1) * 0.5);
from.style.top = (to_rect.top + height) + "px";
from.style.left = (to_rect.left + width) + "px";
} // no error
However, taking this route allows for various problematic calls to adjustElements
which may lead to unexpected behavior:
adjustElements("oopsie"); // no error
This permissive setup also enables erroneous implementations; for instance, in the aforementioned code, misspellings such as getBoundingClientRect
and to_rect.height
go unnoticed by the compiler due to the broadness of any
. Hence, avoiding such an approach is advised.
Instead, establishing the correct type for the function parameter is crucial. As determined earlier, the from
and to
attributes should be of type HTMLElement
, while the coords
attribute ought to represent a tuple consisting of two numeric elements.
You could define a named interface
for this object structure. However, if this structure is only utilized once, creating an explicit name might not be necessary. In such cases, employing an anonymous object type suffices:
export const adjustElements = ({ from, to, coords }:
{ from: HTMLElement, to: HTMLElement, coords: [number, number] }
) => {
let to_rect = to.getBoundingClientRect(),
width = to_rect.width * ((coords[0] - 1) * 0.5),
height = to_rect.height * ((coords[1] - 1) * 0.5);
from.style.top = (to_rect.top + height) + "px";
from.style.left = (to_rect.left + width) + "px";
}
With this adjustment, the code operates effectively, errors are promptly flagged by the compiler, and illegitimate invocations of adjustElements()
are identified:
adjustElements("oopsie"); // error
// Argument of type 'string' is not assignable to parameter of
// type '{ from: HTMLElement; to: HTMLElement; coords: [number, number]; }
An occurrence considered invalid by the compiler includes:
adjustElements({
from: document.querySelector(".wheel-pointer"),
//~~~~ <-- Type 'HTMLElement | null' is not assignable to type 'HTMLElement'
to: document.querySelector(".wheel"),
//~~ <-- Type 'HTMLElement | null' is not assignable to type 'HTMLElement'
coords: [2, 1]
}) // error!
The resulting errors pinpoint the issue accurately: the likelihood that the from
and to
properties may hold a value of null
due to the ambiguous return of document.querySelector()
. To rectify this, ensure that these properties are non-null either through verification or assertion. Verification utilizing truthiness narrowing involves:
const wheelPointerElement = document.querySelector(".wheel-pointer");
if (!wheelPointerElement) throw new Error("OH NO");
const wheelElement = document.querySelector(".wheel");
if (!wheelElement) throw new Error("WHY ME");
adjustElements({
from: wheelPointerElement,
to: wheelElement,
coords: [2, 1]
}) // okay
Whereas assertion via the non-null assertion operator !
entails:
adjustElements({
from: document.querySelector(".wheel-pointer")!,
to: document.querySelector(".wheel")!,
coords: [2, 1]
}) // okay
To delve into the nuances between checking and asserting, along with their respective advantages and drawbacks, requires extensive elaboration beyond the scope of this discussion. The core principle remains—such error scenarios exemplify TypeScript's preemptive error notification feature. Unlike traditional JavaScript deployments, TypeScript offers forewarning on potential runtime issues like calling
adjustElements(null, null, [2, 1])
, prompting the developer to address them beforehand.
Access Playground link for the code