reactjs Typescript React 'isValidElement'类型保护未按预期工作

vuktfyat  于 2022-12-03  发布在  React
关注(0)|答案(1)|浏览(171)

I have a tooltip component implemented like this (I have simplified it), which can be used in two different ways by passing a value to tooltip prop, either it will be a JSX element or a callback function that returns a ReactElement eventually:

import { isValidElement, ReactNode, useCallback, useState } from 'react';
import { Portal } from 'react-portal';

interface ChildrenProps {
    onHide: () => void;
    onShow: () => void;
}

type TooltipCallback = (onHide: () => void) => ReactNode;

export interface Props {
    tooltip: ReactNode | TooltipCallback;
    children: (props: ChildrenProps) => ReactNode;
}

export function Tooltip({ children, tooltip }: Props) {
    const [show, setShow] = useState(false);

    const onHide = useCallback(() => {
        setShow(false);
    }, []);

    const onShow = useCallback(() => {
        setShow(true);
    }, []);

    return (
        <>
            {children({ onHide, onShow })}
            <Portal>
                {show && tooltip !== null && tooltip !== undefined && (
                    <div className="tooltip">
                        {/* TS complains here */}
                        {isValidElement(tooltip) ? tooltip : tooltip(onHide)}
                    </div>
                )}
            </Portal>
        </>
    );
}

Usage by passing a callback function to tooltip :

<Tooltip
    tooltip={(onHide) => (
        <div>
            <span>
                In this tooltip, the only way to close it is by clicking the close button in the tooltip
            </span>
            <button onClick={onHide}>close</button>
        </div>
    )}
>
    {({ onShow }) => <span onMouseOver={onShow}>Some text that triggers tooltip</span>}
</Tooltip>

Usage by passing a ReactElement to tooltip :

<Tooltip
    tooltip={
        <span>This is a tooltip that will be closed when goes away when your mouse leaves</span>
    }
>
    {({ onShow, onHide }) => (
        <span onMouseLeave={onHide} onMouseOver={onShow}>
            Some text that triggers tooltip
        </span>
    )}
</Tooltip>;

But typescript is complaining about it:

This expression is not callable.
  Not all constituents of type 'string | number | boolean | {} | ReactNodeArray | TooltipCallback' are callable.
    Type 'string' has no call signatures.ts(2349)

However, I assumed since I have the isValidElement(tooltip) check, then it is guaranteed that type of the tooltip is not string | number | {} | ReactNodeArray when trying to render the tooltip with callback function in the else clause.
How do I fix this TS error?

qkf9rpyu

qkf9rpyu1#

您的程式码问题在于工具提示属性宣告为类型ReactNode | TooltipCallback,因此TypeScript不知道它在条件的else子句中永远是TooltipCallback。
若要修正这个问题,您可以在else子句的tooltip变数中加入判断提示,告诉TypeScript它在该点上绝对是TooltipCallback。您可以在tooltip变数之后加入做为TooltipCallback来完成这个动作,如下所示:

{isValidElement(tooltip) ? tooltip : (tooltip as TooltipCallback)(onHide)}

通过此更改,TypeScript将不再抱怨else子句中对tooltip()的调用。
以下是应用了更改的完整代码:

import { isValidElement, ReactNode, useCallback, useState } from 'react';
import { Portal } from 'react-portal';

interface ChildrenProps {
    onHide: () => void;
    onShow: () => void;
}

type TooltipCallback = (onHide: () => void) => ReactNode;

export interface Props {
    tooltip: ReactNode | TooltipCallback;
    children: (props: ChildrenProps) => ReactNode;
}

export function Tooltip({ children, tooltip }: Props) {
    const [show, setShow] = useState(false);

    const onHide = useCallback(() => {
        setShow(false);
    }, []);

    const onShow = useCallback(() => {
        setShow(true);
    }, []);

    return (
        <>
            {children({ onHide, onShow })}
            <Portal>
                {show && tooltip !== null && tooltip !== undefined && (
                    <div className="tooltip">
                        {/* TS no longer complains here */}
                        {isValidElement(tooltip) ? tooltip : (tooltip as TooltipCallback)(onHide)}
                    </div>
                )}
            </Portal>
        </>
    );
}

相关问题