简体   繁体   中英

TypeScript: functions without interface

Is it allowed to use function props without any interface?

I have a function with properties:

from - HTML Element

to - HTML Element

coords - Array [2, 2]

export const adjustElements = ({ from, to, coords }) => {
    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";
}

But when I run the code I get following errors:

Binding element 'from' implicitly has an 'any' type.

Binding element 'to' implicitly has an 'any' type.

Binding element 'coords' implicitly has an 'any' type.

It happens because that I didn't add any interface to the function, but the problem is that I have a lot of functions like this, but with different parameters, so writing to everyone a new interface would be crazy... Is there a way where typescript could just stop requiring an interface for every function?

EDIT: As jcalz pointed out in a comment, the answer I've given isn't valid TypeScript. I had missed that you were passing a single argument wrapped in {} rather than passing in three separate arguments.


You need to tell TypeScript what the types of your function's arguments are meant to be. If you don't, and TypeScript can't tell somehow (eg if they had a default value), then it will infer their type as any . But the default compiler settings prevent implicit any types.

You don't need to use an interface, you can do this:

export const adjustElements = ({ from: HTMLElement, to: HTMLElement, coords: [number, number] }) => {

Since the compiler is unable to infer the types of the from , to , and coords variables destructured from the function parameter, it falls back to inferring the any type which is intentionally unsafe and allows you to do almost anything with no further error. Assuming you've got the --strict suite of compiler features enabled (and you probably should) or even just the --noImplicitAny compiler option enabled, this automatic fallback to any will generate a warning, so that you are completely aware that the loss of type safety is happening.


The right thing to do is to annotate the function parameter to tell the compiler what type it will be. If you want to use any and just silence the warning, you can do so explicitly:

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

but this allows all kinds of crazy calls to adjustElements where you'd probably want to see a compiler error:

adjustElements("oopsie"); // no error

as well as allowing crazy implementations ; consider that in the above code I completely misspelled getBoundingClientRect and to_rect.height but the compiler failed to catch either mistake, because both to and to_rect are of type any and the compiler gives up on worrying what you're doing with those.

So you don't want to do that.


Instead, you should think about what type the function parameter must be. You've already done this: the from and to properties should be HTMLElement , and the coords property should be a tuple of length two where each element is a number .

You could make a named interface for this object type, but if you're only ever going to use it once, you might not want to bother giving it an explicit name. In that case you can just use an anonymous object type :

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";
}

Now this works and the compiler will catch misspellings, and it will also catch bogus calls to adjustElements() :

adjustElements("oopsie"); // error
// Argument of type 'string' is not assignable to parameter of 
// type '{ from: HTMLElement; to: HTMLElement; coords: [number, number]; }

That's an error because you gave it a string where it expected your anonymous object type, and a string is not appropriate.


Note that the compiler considers the following a bogus call also:

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 errors tell you exactly the problem: the from and to properties may well be null , because document.querySelector() sometimes returns null . The compiler does not know that anything of class "wheel-pointer" or "wheel" exists in the document, so it's an error. If you want to make a call like this you will need to either check that those aren't null , or assert that they aren't. A check could look like this (using truthiness narrowing ):

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

While an assertion could look like this: (using the non-null assertion operator ! ):

adjustElements({
  from: document.querySelector(".wheel-pointer")!,
  to: document.querySelector(".wheel")!,
  coords: [2, 1]
}) // okay

It's too far afield from the original question to explain the differences between checking and asserting, pros and cons of each, and the different ways to do them. Just know that these error situations are part of the point of TypeScript in the first place. In your original JavaScript code, you'd have to wait until runtime to see errors on adjustElements(null, null, [2, 1]) . TypeScript warns you ahead of time that something like that might happen at runtime and gives you a chance to address it.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM