Link.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import React from "react";
  2. import { __RouterContext as RouterContext } from "react-router";
  3. import PropTypes from "prop-types";
  4. import invariant from "tiny-invariant";
  5. import { resolveToLocation, normalizeToLocation } from "./utils/locationUtils";
  6. // React 15 compat
  7. const forwardRefShim = C => C;
  8. let { forwardRef } = React;
  9. if (typeof forwardRef === "undefined") {
  10. forwardRef = forwardRefShim;
  11. }
  12. function isModifiedEvent(event) {
  13. return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
  14. }
  15. const LinkAnchor = forwardRef(
  16. (
  17. {
  18. innerRef, // TODO: deprecate
  19. navigate,
  20. onClick,
  21. ...rest
  22. },
  23. forwardedRef
  24. ) => {
  25. const { target } = rest;
  26. let props = {
  27. ...rest,
  28. onClick: event => {
  29. try {
  30. if (onClick) onClick(event);
  31. } catch (ex) {
  32. event.preventDefault();
  33. throw ex;
  34. }
  35. if (
  36. !event.defaultPrevented && // onClick prevented default
  37. event.button === 0 && // ignore everything but left clicks
  38. (!target || target === "_self") && // let browser handle "target=_blank" etc.
  39. !isModifiedEvent(event) // ignore clicks with modifier keys
  40. ) {
  41. event.preventDefault();
  42. navigate();
  43. }
  44. }
  45. };
  46. // React 15 compat
  47. if (forwardRefShim !== forwardRef) {
  48. props.ref = forwardedRef || innerRef;
  49. } else {
  50. props.ref = innerRef;
  51. }
  52. return <a {...props} />;
  53. }
  54. );
  55. if (__DEV__) {
  56. LinkAnchor.displayName = "LinkAnchor";
  57. }
  58. /**
  59. * The public API for rendering a history-aware <a>.
  60. */
  61. const Link = forwardRef(
  62. (
  63. {
  64. component = LinkAnchor,
  65. replace,
  66. to,
  67. innerRef, // TODO: deprecate
  68. ...rest
  69. },
  70. forwardedRef
  71. ) => {
  72. return (
  73. <RouterContext.Consumer>
  74. {context => {
  75. invariant(context, "You should not use <Link> outside a <Router>");
  76. const { history } = context;
  77. const location = normalizeToLocation(
  78. resolveToLocation(to, context.location),
  79. context.location
  80. );
  81. const href = location ? history.createHref(location) : "";
  82. const props = {
  83. ...rest,
  84. href,
  85. navigate() {
  86. const location = resolveToLocation(to, context.location);
  87. const method = replace ? history.replace : history.push;
  88. method(location);
  89. }
  90. };
  91. // React 15 compat
  92. if (forwardRefShim !== forwardRef) {
  93. props.ref = forwardedRef || innerRef;
  94. } else {
  95. props.innerRef = innerRef;
  96. }
  97. return React.createElement(component, props);
  98. }}
  99. </RouterContext.Consumer>
  100. );
  101. }
  102. );
  103. if (__DEV__) {
  104. const toType = PropTypes.oneOfType([
  105. PropTypes.string,
  106. PropTypes.object,
  107. PropTypes.func
  108. ]);
  109. const refType = PropTypes.oneOfType([
  110. PropTypes.string,
  111. PropTypes.func,
  112. PropTypes.shape({ current: PropTypes.any })
  113. ]);
  114. Link.displayName = "Link";
  115. Link.propTypes = {
  116. innerRef: refType,
  117. onClick: PropTypes.func,
  118. replace: PropTypes.bool,
  119. target: PropTypes.string,
  120. to: toType.isRequired
  121. };
  122. }
  123. export default Link;