Welcome to MLink Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

javascript - React + TypeScript: Scroll event type not assignable to window.addEventListener

Can it be that TypeScript DOM types for events and React Event types don't go well with another?

Look at this code:

  useEffect(() => {
    const onScroll = (event: React.ChangeEvent<Body>) => {
      console.log("event", event.target);
    };

    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, []);

It throws

No overload matches this call.
  Overload 1 of 2, '(type: "scroll", listener: (this: Window, ev: Event) => any, options?: boolean | AddEventListenerOptions | undefined): void', gave the following error.
    Argument of type '(event: ChangeEvent<Body>) => void' is not assignable to parameter of type '(this: Window, ev: Event) => any'.
      Types of parameters 'event' and 'ev' are incompatible.
        Type 'Event' is not assignable to type 'ChangeEvent<Body>'.

Than I tried the following. This compiles correctly.

  useEffect(() => {
    const onScroll = (event: Event) => { // <--- changed event type here
      console.log("event", event.target);
    };

    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, []);

event.target can not be access without compile errors, though. I checked the following function call (it is working in the browser if I ts-ignore the error):

  useEffect(() => {
    const onScroll = (event: Event) => {
      console.log(
        "event",
        event?.target?.activeElement.getBoundingClientRect() // <--- this function call is actually working (ts-ignored it), even if TypeScript throws an error
      );
    };

    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, []);

throws

TypeScript error in /home/sirhennihau/Workspace/giphy-search/src/Components/grid.tsx(27,24):
Property 'activeElement' does not exist on type 'EventTarget'.  TS2339

    25 |       console.log(
    26 |         "event",
  > 27 |         event?.target?.activeElement.getBoundingClientRect()
       |                        ^
    28 |       );
    29 |     };
    30 |

My dependencies:

    "@types/node": "^12.0.0",
    "@types/react": "^16.9.53",
    "@types/react-dom": "^17.0.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "typescript": "^4.0.3"

Are maybe just the typings bad or am I doing something wrong?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Well, yes, React-Events and DOM-Events sometimes don't "go well" with one another, but there are clean ways to handle your use case (and most of all use cases in React, in my experience):

DOM events

The DOM methond window.addEventListener expects a DOM-EventListener ( (evt: Event): void;), so you need to use the DOM-Event interface (as you did).

Here I added some types to your code to illustrate this:

useEffect(() => {
    const onScroll: EventListener = (event: Event) => { // <-- DOM-EventListener
        console.log("event", event.target);
    };

    const win: Window = window;   // <-- DOM-Window, extends DOM-EventTarget
    win.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
}, []);

event.target

The event.target should be accessible. I assume you mean that you get errors if you try to acces event.target.activeElement.

event.target.activeElement throws an error, because Event could be any event, event.target could be any element that extends EventTarget | null, which is no specific element, and maybe has no activeElement.

But it works despite of the TS error, because in reality it is an element that has the .activeElement.

const onScroll: EventListener = (event: Event) => {
    const targetAny: EventTarget | null = event.target; // <-- maybe has no .activeElement
    console.log( targetAny.activeElement );
};

If you are sure what the element is, you might type assert the specific element:

const onScroll: EventListener = (event: Event) => {
    const targetDiv: HTMLDocument = event.target as HTMLDocument; // <-- assert DOM-HTMLDocument
    console.log("DOM event", targetDiv.activeElement);    // <-- activeElement works
};

React events

The React-Types should be used with React-Elements.

You still would have to assert the type of event.target to access the properties of a specific element (here I use .style), because the .target of the React.SyntheticEvent (which extends React.BaseSyntheticEvent) is exactly the same DOM-EventTarget as the DOM-Event uses.

To be clear: yes, React also uses some DOM-Types, so here React "goes well" with DOM-Events.

const onScrollReact: React.EventHandler<React.UIEvent<ReactNode>> = (event: React.UIEvent<React.ReactNode> ) => {
    const target: EventTarget = event.target;     // <-- React uses the DOM EventTarget
    const targetDiv: HTMLDivElement = target as HTMLDivElement; // <-- type assert DOM-HTMLDivElement
    console.log("React event", target.style);     // <-- throws TS error
    console.log("React event", targetDiv.style);  // <-- no TS error
};

return (<div
    onScroll={ onScrollReact }
    style={{ height: '400px', overflow: 'scroll' }}
>
    <div style={{ height: '2000px', backgroundColor: '#eee' }}>
        try scroll event
    </div>
</div>);

You could alternatively use the DOM-HTMLDivElement in this example (the behavior is exactly the same, but it is more 'declarative', I guess):

const onScrollReact2: React.EventHandler<React.UIEvent<HTMLDivElement>> = (event: React.UIEvent<HTMLDivElement>) => {
    console.log("React event", event.target.style); // <-- still throws TS error
};

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to MLink Developer Q&A Community for programmer and developer-Open, Learning and Share
...