【问题标题】:NextJS render Link with createElementNextJS 使用 createElement 渲染链接
【发布时间】:2021-06-02 13:34:49
【问题描述】:

我正在尝试在 NextJS 中使用 createElement 呈现链接,但它不起作用。

代码:

import {createElement} from "react";
import Link from "next/link";
createElement(
    item.href ? Link : "div",
    {
        href: item.href
    }, <button>{item.title}</button>
)

结果:

【问题讨论】:

    标签: javascript reactjs next.js


    【解决方案1】:

    链接在node_modules/next/dist/client/link.d.ts中定义如下

    /// <reference types="node" />
    import React from 'react';
    import { UrlObject } from 'url';
    declare type Url = string | UrlObject;
    export declare type LinkProps = {
        href: Url;
        as?: Url;
        replace?: boolean;
        scroll?: boolean;
        shallow?: boolean;
        passHref?: boolean;
        prefetch?: boolean;
        locale?: string | false;
    };
    declare function Link(props: React.PropsWithChildren<LinkProps>): React.DetailedReactHTMLElement<{
        onMouseEnter?: ((event: React.MouseEvent<Element, MouseEvent>) => void) | undefined;
        onClick: (event: React.MouseEvent<Element, MouseEvent>) => void;
        href?: string | undefined;
        ref?: any;
    }, HTMLElement>;
    export default Link;
    

    您必须导入 LinkProps 或自己定义该类型

    属性href 的类型为Url,其定义为

    
    declare type Url = string | UrlObject
    
    

    UrlObject 定义为

        interface UrlObject {
            auth?: string | null;
            hash?: string | null;
            host?: string | null;
            hostname?: string | null;
            href?: string | null;
            pathname?: string | null;
            protocol?: string | null;
            search?: string | null;
            slashes?: boolean | null;
            port?: string | number | null;
            query?: string | null | ParsedUrlQueryInput;
        }
    
    

    ParsedUrlQueryInput 在哪里

    
    interface ParsedUrlQueryInput extends NodeJS.Dict<string | number | boolean | ReadonlyArray<string> | ReadonlyArray<number> | ReadonlyArray<boolean> | null> {}
    
    

    这是interface ParsedUrlQuery 的输入值,接下来使用 router.query/context.query 或 context.params(在 getStaticProps 中)获得。 ParsedUrlQuery 是字典类型:

    interface ParsedUrlQuery extends NodeJS.Dict<string | string[]> { }
    

    唯一可行的方法是如果您获得LinkProps 或自己定义它们,那么您必须使用React.forwardRef 并合并注入Links 属性的自定义组件的引用...或者,你可以尝试使用

    let linkType: typeof Link
    

    您希望实现这一目标的用例是什么?我之所以问,是因为我很困惑为什么你想要一个自定义链接,而不是一个自定义锚组件,如果锚标签包装了一个反应功能零件。我问的原因是因为我遇到了一个问题,我试图用数据(卡片)包装一个功能组件,如下所示:

    <>
        <Link
            passHref
            href={`/services/[slug]`}
            as={`/services/${encodeURIComponent(
                popservice.node.slug
            )}`}
            scroll={true}
        >
            <a className='block' key={i++}>
                <ServiceTopTemplate service={popservice.node} />
            </a>
        </Link>
    </>
    

    这并不是完美无缺的,经过一番挖掘,我发现如果你用Link-&gt;a-&gt;FC 包装一个诸如ServiceTopTemplate 的功能组件,你需要一个带有forwardedRef 的锚组件来路由到正确嵌入锚中。所以,我通过使用 forwardedRef 制作一个不可知的、可重用的 Anchor 组件来解决这个问题

    <>
        <Link
            passHref
            href={`/services/[slug]`}
            as={`/services/${encodeURIComponent(
                popservice.node.slug
            )}`}
            scroll={true}
        >
            <Anchor className='block' key={i++}>
                <ServiceTopTemplate service={popservice.node} />
            </Anchor>
        </Link>
    </>
    

    自定义Anchor组件定义如下:

    import mergeRefs from 'react-merge-refs';
    import { LoadingSpinner } from '@/components/UI';
    import cn from 'classnames';
    import css from './anchor.module.css';
    import React, {
        FC,
        CSSProperties,
        AnchorHTMLAttributes,
        useRef,
        JSXElementConstructor
    } from 'react';
    import { Url } from 'url';
    
    // Omit intrinsic href to reinject with conditional Url interface to accept query hrefs from next/link
    // query hrefs, e.g.: href={{ query: { slug }, pathname: '', href: '', { ...options, etc } }}
    
    export interface ForwardedAnchorProps<T = HTMLAnchorElement>
        extends Omit<AnchorHTMLAttributes<T>, 'href'> {
        className?: string;
        Component?: string | JSXElementConstructor<any>;
        width?: string | number;
        variant?: 'flat' | 'slim';
        loading?: boolean;
        type?: 'submit' | 'link' | 'active';
        href?: string | Url;
        active?: boolean;
        disabled?: boolean;
        style?: CSSProperties | {};
    }
    
    // this component serves to wrap imported functional components -- allows for proper embedding of href in browser when
    // wrapping functional components with Link and anchor, respectively
    
    const Anchor: FC<ForwardedAnchorProps<HTMLAnchorElement>> =
        React.forwardRef((props, aRef) => {
            const {
                onClick,
                width,
                className,
                href,
                active,
                style,
                variant = 'flat',
                disabled = false,
                loading = false,
                Component = 'a',
                children,
                ...rest
            } = props;
    
            const ref = useRef<typeof Component>(null);
    
            const rootClassName = cn(
                css.root,
                {
                    [css.slim]: variant === 'slim',
                    [css.loading]: loading,
                    [css.disabled]: disabled
                },
                className
            );
    
            return (
                <Component
                    aria-pressed={active}
                    disabled={disabled}
                    ref={mergeRefs([ref, aRef])}
                    className={rootClassName}
                    onClick={onClick}
                    href={href}
                    style={{ width, ...style }}
                    {...rest}
                >
                    {children}
                    {loading && (
                        <i className='pl-2 m-0 flex'>
                            <LoadingSpinner className='transform-gpu transition-all duration-150 animate-spin' />
                        </i>
                    )}
                </Component>
            );
        });
    export default Anchor;
    
    

    对应css文件的内容:

    .root {
        @apply cursor-pointer block transition-all ease-in-out duration-150 transform-gpu;
    }
    
    .root:hover {
        @apply bg-redditBG;
    }
    
    .root:focus {
        @apply outline-none;
    }
    
    .root[data-active] {
        @apply hover:bg-redditBG;
    }
    
    .loading {
        @apply bg-current text-olive-300 border-redditBG cursor-not-allowed;
    }
    
    .slim {
        @apply py-2 transform-none normal-case;
    }
    
    .disabled,
    .disabled:hover {
        @apply text-olive-400 border-redditBG bg-current bg-opacity-90 cursor-not-allowed;
        filter: grayscale(1);
        -webkit-transform: translateZ(0);
        -webkit-perspective: 1000;
        -webkit-backface-visibility: hidden;
    }
    
    

    更新 -- 重新检查您的代码后,尝试更改此代码:

    <button>{item.title}</button>
    

    到这里:

    <a><button>{item.title}</button></a>
    

    那么它可能会起作用

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-22
      • 1970-01-01
      • 2022-01-01
      • 2013-02-28
      • 1970-01-01
      • 2022-12-03
      • 1970-01-01
      相关资源
      最近更新 更多