react-router-dom: Correct type for ref in Link<S> (#43428)

* no-op: Run prettier --write on react-router-dom.

* Allow correct "ref" to be passed Link.

"ref" can be used rather than "innerRef" to return a ref to the <a>
element inside of <Link>.

See:

[1]: reacttraining.com/react-router/web/api/Link/innerref-function

  Where it says:

  > As of React Router 5.1, if you are using React 16 you should not
  > need this prop because we forward this ref to the underlying <a>.
  > Use a normal ref instead.Allows access to the underlying ref of the
  > component.

[2]: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js#L74-L121

  Link is the result of forwardRef rather than an actual React class.

  This implies a few things:

  1. It is a function (see the return type of
      React.ForwardRefExoticComponent) rather than a class
  2. Its reference is to an inner <a> element, rather than what's in
    React.Component, which is a Ref to the element itself.

This introduces a potential incompatibility where users can't use
"new Link" anymore. But that reflects reality (Link is callable not
newable).

Since Link was previously exported as a class, we need to continue to
support it being used as both a type and a value. Since Link was
exported as a generic, we need to continue to support that. As a result:

1. Link is exported as a function that happens to implement the type
  corresponding to the result of
  `forwardRef<HTMLAnchorElement, LinkProps<S>>`.

2. Link is still exported as a type (interface) which is the same as
  that of the return value of `forwardRef<...>`.

* Make sure Link<S> can be used as a generic.

This is is only supported in TS >= 2.9.

I bumped the minimum TS supported version to 2.9 since 2.8 support ends this
month anyway.

* Apply prettier --write to tests.

* Apply same change (forwardRef) to NavLink.

1. https://reacttraining.com/react-router/web/api/NavLink

    NavLink is a version of <Link> with the same props.

2. NavLink is similarly implemented in terms of forwardRef, so all
    the justification from the previous commit applies. See:

    https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js

* To maintain TS 2.8 compatibility, use
React.createElement to test Link<{foo:number}>
and NavLink.

When TS 2.8 compatibility is no longer needed, this CL can be reverted
as-is.
This commit is contained in:
Eyas 2020-04-10 22:34:10 -04:00 committed by GitHub
parent b6dcecae25
commit 6f7d6c01da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 27 deletions

View File

@ -9,7 +9,7 @@
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
import { match } from "react-router";
import { match } from 'react-router';
import * as React from 'react';
import * as H from 'history';
@ -39,7 +39,7 @@ export {
export interface BrowserRouterProps {
basename?: string;
getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void);
getUserConfirmation?: (message: string, callback: (ok: boolean) => void) => void;
forceRefresh?: boolean;
keyLength?: number;
}
@ -47,7 +47,7 @@ export class BrowserRouter extends React.Component<BrowserRouterProps, any> {}
export interface HashRouterProps {
basename?: string;
getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void);
getUserConfirmation?: (message: string, callback: (ok: boolean) => void) => void;
hashType?: 'slash' | 'noslash' | 'hashbang';
}
export class HashRouter extends React.Component<HashRouterProps, any> {}
@ -58,23 +58,28 @@ export interface LinkProps<S = H.LocationState> extends React.AnchorHTMLAttribut
replace?: boolean;
innerRef?: React.Ref<HTMLAnchorElement>;
}
export class Link<S = H.LocationState> extends React.Component<
LinkProps<S>,
any
> {}
export function Link<S = H.LocationState>(
// TODO: Define this as ...params: Parameters<Link<S>> when only TypeScript >= 3.1 support is needed.
props: React.PropsWithoutRef<LinkProps<S>> & React.RefAttributes<HTMLAnchorElement>,
): ReturnType<Link<S>>;
export interface Link<S = H.LocationState>
extends React.ForwardRefExoticComponent<
React.PropsWithoutRef<LinkProps<S>> & React.RefAttributes<HTMLAnchorElement>
> {}
export interface NavLinkProps<S = H.LocationState> extends LinkProps<S> {
activeClassName?: string;
activeStyle?: React.CSSProperties;
exact?: boolean;
strict?: boolean;
isActive?<Params extends { [K in keyof Params]?: string }>(
match: match<Params>,
location: H.Location<S>,
): boolean;
location?: H.Location<S>;
activeClassName?: string;
activeStyle?: React.CSSProperties;
exact?: boolean;
strict?: boolean;
isActive?<Params extends { [K in keyof Params]?: string }>(match: match<Params>, location: H.Location<S>): boolean;
location?: H.Location<S>;
}
export class NavLink<S = H.LocationState> extends React.Component<
NavLinkProps<S>,
any
> {}
export function NavLink<S = H.LocationState>(
// TODO: Define this as ...params: Parameters<NavLink<S>> when only TypeScript >= 3.1 support is needed.
props: React.PropsWithoutRef<NavLinkProps<S>> & React.RefAttributes<HTMLAnchorElement>,
): ReturnType<NavLink<S>>;
export interface NavLink<S = H.LocationState>
extends React.ForwardRefExoticComponent<
React.PropsWithoutRef<NavLinkProps<S>> & React.RefAttributes<HTMLAnchorElement>
> {}

View File

@ -5,19 +5,17 @@ import * as H from 'history';
const getIsActive = (extraProp: string) => (match: match, location: H.Location) => !!extraProp;
interface Props extends NavLinkProps {
extraProp: string;
extraProp: string;
}
export default function(props: Props) {
const {extraProp, ...rest} = props;
const isActive = getIsActive(extraProp);
return (
<NavLink {...rest} isActive={isActive}/>
);
const { extraProp, ...rest } = props;
const isActive = getIsActive(extraProp);
return <NavLink {...rest} isActive={isActive} />;
}
type OtherProps = RouteComponentProps<{
id: string;
id: string;
}>;
const Component: React.FC<OtherProps> = props => {
@ -39,7 +37,20 @@ const MyLink: React.FC<LinkProps> = props => <Link style={{ color: 'red' }} {...
const refCallback: React.Ref<HTMLAnchorElement> = node => {};
<Link to="/url" replace={true} innerRef={refCallback} />;
<Link to="/url" replace={true} ref={refCallback} />;
<NavLink to="/url" replace={true} innerRef={refCallback} />;
<NavLink to="/url" replace={true} ref={refCallback} />;
const ref = React.createRef<HTMLAnchorElement>();
<Link to="/url" replace={true} innerRef={ref} />;
<Link to="/url" replace={true} ref={ref} />;
<NavLink to="/url" replace={true} innerRef={ref} />;
<NavLink to="/url" replace={true} ref={ref} />;
<Link to="/url" aria-current="page" />;
React.createElement<LinkProps<{ foo: number }> & React.RefAttributes<HTMLAnchorElement>>(Link, {
to: { pathname: 'abc', state: { foo: 5 } },
});
React.createElement<NavLinkProps<{ foo: number }> & React.RefAttributes<HTMLAnchorElement>>(NavLink, {
to: { pathname: 'abc', state: { foo: 5 } },
});