Route.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import React from "react";
  2. import { isValidElementType } from "react-is";
  3. import PropTypes from "prop-types";
  4. import invariant from "tiny-invariant";
  5. import warning from "tiny-warning";
  6. import RouterContext from "./RouterContext.js";
  7. import matchPath from "./matchPath.js";
  8. function isEmptyChildren(children) {
  9. return React.Children.count(children) === 0;
  10. }
  11. function evalChildrenDev(children, props, path) {
  12. const value = children(props);
  13. warning(
  14. value !== undefined,
  15. "You returned `undefined` from the `children` function of " +
  16. `<Route${path ? ` path="${path}"` : ""}>, but you ` +
  17. "should have returned a React element or `null`"
  18. );
  19. return value || null;
  20. }
  21. /**
  22. * The public API for matching a single path and rendering.
  23. */
  24. class Route extends React.Component {
  25. render() {
  26. return (
  27. <RouterContext.Consumer>
  28. {context => {
  29. invariant(context, "You should not use <Route> outside a <Router>");
  30. const location = this.props.location || context.location;
  31. const match = this.props.computedMatch
  32. ? this.props.computedMatch // <Switch> already computed the match for us
  33. : this.props.path
  34. ? matchPath(location.pathname, this.props)
  35. : context.match;
  36. const props = { ...context, location, match };
  37. let { children, component, render } = this.props;
  38. // Preact uses an empty array as children by
  39. // default, so use null if that's the case.
  40. if (Array.isArray(children) && isEmptyChildren(children)) {
  41. children = null;
  42. }
  43. return (
  44. <RouterContext.Provider value={props}>
  45. {props.match
  46. ? children
  47. ? typeof children === "function"
  48. ? __DEV__
  49. ? evalChildrenDev(children, props, this.props.path)
  50. : children(props)
  51. : children
  52. : component
  53. ? React.createElement(component, props)
  54. : render
  55. ? render(props)
  56. : null
  57. : typeof children === "function"
  58. ? __DEV__
  59. ? evalChildrenDev(children, props, this.props.path)
  60. : children(props)
  61. : null}
  62. </RouterContext.Provider>
  63. );
  64. }}
  65. </RouterContext.Consumer>
  66. );
  67. }
  68. }
  69. if (__DEV__) {
  70. Route.propTypes = {
  71. children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
  72. component: (props, propName) => {
  73. if (props[propName] && !isValidElementType(props[propName])) {
  74. return new Error(
  75. `Invalid prop 'component' supplied to 'Route': the prop is not a valid React component`
  76. );
  77. }
  78. },
  79. exact: PropTypes.bool,
  80. location: PropTypes.object,
  81. path: PropTypes.oneOfType([
  82. PropTypes.string,
  83. PropTypes.arrayOf(PropTypes.string)
  84. ]),
  85. render: PropTypes.func,
  86. sensitive: PropTypes.bool,
  87. strict: PropTypes.bool
  88. };
  89. Route.prototype.componentDidMount = function() {
  90. warning(
  91. !(
  92. this.props.children &&
  93. !isEmptyChildren(this.props.children) &&
  94. this.props.component
  95. ),
  96. "You should not use <Route component> and <Route children> in the same route; <Route component> will be ignored"
  97. );
  98. warning(
  99. !(
  100. this.props.children &&
  101. !isEmptyChildren(this.props.children) &&
  102. this.props.render
  103. ),
  104. "You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored"
  105. );
  106. warning(
  107. !(this.props.component && this.props.render),
  108. "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored"
  109. );
  110. };
  111. Route.prototype.componentDidUpdate = function(prevProps) {
  112. warning(
  113. !(this.props.location && !prevProps.location),
  114. '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
  115. );
  116. warning(
  117. !(!this.props.location && prevProps.location),
  118. '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
  119. );
  120. };
  121. }
  122. export default Route;