动画反应路线不适用于 <Switch> 组件

Animated react routes not working with <Switch> component

我正在尝试为我的 React 应用程序构建一个简单的路由器,它将包含一些从对象数组生成的路由和一个静态 404 路由。要求是每个路由之间的转换必须是动画的。

我正在为浏览器使用 react-routerreact-transition-group

我想实现这样的目标(精简、不完整的伪代码):

const routes = [
    { path: "/", component: Home },
    { path: "/about", component: About },
    { path: "/contact", component: Contact }
];

const createRoute = (route) => {
    return (
        <CSSTransition className="page" timeout={300}>
            <Route path={route.path} exact component={route.component} />
        </CSSTransition>
    );
}

<Router>
    {routes.map(createRoute)}
    <CSSTransition className="page" timeout={300}>
        <Route component={PageNotFound} />
    </CSSTransition>
</Router>

此代码的完整版本可在此 Codesandbox 上找到:

https://codesandbox.io/s/react-router-switch-csstransition-catch-all-route-bug-forked-qzt9g

我需要使用 <Switch> 组件来防止 404 路由显示在应用程序的所有其他路由中,但是一旦我添加 <Switch>,动画就会停止工作。

在上面的示例中,尽管遵循了 react-routerreact-transition-group 官方文档中的指南,但您会发现与 <Switch> 一起使用时动画不起作用。

然而,它们在不使用 <Switch> 的情况下也能完美运行,但当然我最终会一直显示 404 路由。

预期结果:

实际结果:

我花了一整天的时间试图找到我遇到的问题的解决方案。不幸的是,我似乎找不到任何可以远程帮助我解决我面临的问题的东西,我搜索了 Google、Stack Overflow、Medium,最后我回到这里希望有人能帮助我请出去。

为了让动画与 Switch 组件一起工作,您必须将正确的 locationwithRouter 传递给 CSSTransition,再加上 TransitionGroup 组件。 我已经使用以下有效解决方案修改了您的沙箱代码:

import React from "react";
import ReactDOM from "react-dom";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import {
  BrowserRouter as Router,
  Route,
  Switch,
  Link,
  withRouter
} from "react-router-dom";

import "./styles.css";

const rootElement = document.getElementById("root");

const components = {
  Home: () => <div>Home page</div>,
  About: () => <div>About the company</div>,
  Contact: () => <div>Contact us</div>,
  NotFound: () => <div>404</div>,
  Menu: ({ links, setIsSwitch }) => (
    <div className="menu">
      {links.map(({ path, component }, key) => (
        <Link key={key} to={path}>
          {component}
        </Link>
      ))}
      <Link to="/404">404</Link>
    </div>
  )
};

const routes = [
  { path: "/", component: "Home" },
  { path: "/about", component: "About" },
  { path: "/contact", component: "Contact" }
];

const createRoutes = (routes) =>
  routes.map(({ component, path }) => {
    const Component = components[component];
    const nodeRef = React.createRef();

    return (
      <Route key={path} path={path} exact>
        {({ match }) => {
          return (
            <div ref={nodeRef} className="page">
              <Component />
            </div>
          );
        }}
      </Route>
    );
  });

const AnimatedSwitch = withRouter(({ location }) => (
  <TransitionGroup>
    <CSSTransition
      key={location.key}
      timeout={500}
      classNames="page"
      unmountOnExit
    >
      <Switch location={location}>{createRoutes(routes)}</Switch>
    </CSSTransition>
  </TransitionGroup>
));

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <components.Menu links={routes} />
      <AnimatedSwitch />
    </Router>
  </React.StrictMode>,
  rootElement
);

这篇文章详细解释了背后的原因:https://latteandcode.medium.com/react-how-to-animate-transitions-between-react-router-routes-7f9cb7f5636a

顺便说一下,withRouter 在 react-router v6 中被弃用了。 所以你应该自己实现那个钩子。

import { useHistory } from 'react-router-dom';

export const withRouter = (Component) => {
  const Wrapper = (props) => {
    const history = useHistory();
    
    return (
      <Component
        history={history}
        {...props}
        />
    );
  };
  
  return Wrapper;
};

请参阅 GitHub 上的弃用问题讨论:https://github.com/remix-run/react-router/issues/7156

一个快速解决方法是您可以执行以下操作

<Switch>
  <Route
    path={"/:page(|about|contact)"}
    render={() => createRoutes(routes)}
  />
  <Route>
    <div className="page">
      <components.NotFound />
    </div>
  </Route>
</Switch>

显然这不是最优雅也不是最可扩展的解决方案。但对于小型反应站点是可行的。

这是工作代码的分支代码和框。 https://codesandbox.io/s/react-router-switch-csstransition-catch-all-route-bug-forked-dhv39

编辑: 我首先检查了 router.location.key,但是通过地址栏访问导致 404 页面呈现每个页面,所以这样更安全。

您可以将路由器作为属性传递给页面,并通过选中 router.location.pathname

有条件地呈现 404 页面
export const routes = [
...all of your routes,
{
    path: "*",
    Component: NotFound,
  },
]


    {routes.map(({ path, pageTitle, Component }) => (
        <Route key={path} exact path={path}>
          {router => (
            <CSSTransition
              in={router.match !== null}
              timeout={400}
              classNames="page"
              unmountOnExit
            >
              <div className="page">
                <Component router={router} />
              </div>
            </CSSTransition>
          )}
        </Route>
      ))}

并且在你的404组件中,你需要检查当前路由的路径是否在所有路由周围可用:

import { routes } from "../Routes";

const checkIfRouteIsValid = path => routes.findIndex(route => route.path === path);

export default function NotFound(props) {
  const pathname = props.router.location.pathname;
  const [show, setShow] = useState(false);
  useEffect(() => {
   setShow(checkIfRouteIsValid(pathname) === -1);
  }, [pathname]);
    return (
      show && (
        <div>Not found!</div>
       )
     );
   }