动画反应路线不适用于 <Switch> 组件
Animated react routes not working with <Switch> component
我正在尝试为我的 React 应用程序构建一个简单的路由器,它将包含一些从对象数组生成的路由和一个静态 404 路由。要求是每个路由之间的转换必须是动画的。
我正在为浏览器使用 react-router
和 react-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-router
和 react-transition-group
官方文档中的指南,但您会发现与 <Switch>
一起使用时动画不起作用。
然而,它们在不使用 <Switch>
的情况下也能完美运行,但当然我最终会一直显示 404 路由。
预期结果:
- 所有路由、动态创建的路由以及静态 404 页面之间的动画转换
实际结果:
- 完全没有动画或总是显示 404 路由的动画
我花了一整天的时间试图找到我遇到的问题的解决方案。不幸的是,我似乎找不到任何可以远程帮助我解决我面临的问题的东西,我搜索了 Google、Stack Overflow、Medium,最后我回到这里希望有人能帮助我请出去。
为了让动画与 Switch 组件一起工作,您必须将正确的 location 和 withRouter 传递给 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>
)
);
}
我正在尝试为我的 React 应用程序构建一个简单的路由器,它将包含一些从对象数组生成的路由和一个静态 404 路由。要求是每个路由之间的转换必须是动画的。
我正在为浏览器使用 react-router
和 react-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-router
和 react-transition-group
官方文档中的指南,但您会发现与 <Switch>
一起使用时动画不起作用。
然而,它们在不使用 <Switch>
的情况下也能完美运行,但当然我最终会一直显示 404 路由。
预期结果:
- 所有路由、动态创建的路由以及静态 404 页面之间的动画转换
实际结果:
- 完全没有动画或总是显示 404 路由的动画
我花了一整天的时间试图找到我遇到的问题的解决方案。不幸的是,我似乎找不到任何可以远程帮助我解决我面临的问题的东西,我搜索了 Google、Stack Overflow、Medium,最后我回到这里希望有人能帮助我请出去。
为了让动画与 Switch 组件一起工作,您必须将正确的 location 和 withRouter 传递给 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
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>
)
);
}