为react-router实现无副作用的路由动画
下面是比较常见的一种react-router动画实现方式:
<TransitionGroup>
<CSSTransition
key={location.key} // 关键位置
classNames="fade"
timeout={300}
>
<Switch location={location}>
<Route exact path="/hsl/:h/:s/:l" component={HSL} />
<Route exact path="/rgb/:r/:g/:b" component={RGB} />
<Route render={() => <div>Not Found</div>} />
</Switch>
</CSSTransition>
</TransitionGroup>
这种方式存在一个比较严重的问题,注意上面CSSTransition传递了当前的location.key作为key,这会导致每次路由切换时整个路由组件完全重载(不是重绘)。
为了说明上面的问题,我举一个例子,假设现在存在一个列表和一个详情页,详情页作为列表的子路由,当从列表进入详情页时,由于location.key发生了改变,列表页会重载并且连续触发unmounted、mounted钩子,当你在列表页订阅了一些数据时,你会发现你在子路由中来回切换时列表在疯狂的请求数据…
当然,你可以完全不使用子路由,这样的话每次跳转都只会简单的用新的路由页面替换旧的路由页面。 不过这种方式会带来一些其他的问题,比如:
当你存在一个复杂的列表页,它包含许多复杂的功能比如虚拟滚动、上拉加载、以及包含多个过滤组件,这种情况下如果不使用子路由,只能考虑把所有列表状态保存到redux或session里,从详情页返回的时候再还原查询数据到查询组件、还原列表状态、回复滚动条位置…,光是想想就?,而使用子路由的话, 你可以轻松的完成下面的效果, 并且组件不会被不必要的重载!
可以查看这个官方示例,它使用Route组件的children来决定何时渲染路由组件,这种方式可以让你更好的控制如何给路由组件添加进入离开动画,并且不用在某个至关重要的位置添加location.key来造成组件不必要的更新:
https://codesandbox.io/s/38qm5m0mz1?from-embed
但是~~,使用这种方式的话又会有两个问题:
- 不能使用Switch组件,不能再使用文档推荐的方式来实现404页面了。
<Switch>
<Route path="/" exact component={Home} />
<Redirect from="/old-match" to="/will-match" />
<Route path="/will-match" component={WillMatch} />
<Route component={NoMatch} />
</Switch>
- 得为每一个组件都传入children,并且children里还要套一个又臭又长的function和CSSTransition。
<Route key={path} exact path={path}>
{({ match }) => (
<CSSTransition
in={match != null}
timeout={300}
classNames="page"
unmountOnExit
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>
好在两个问题都比较好解决,下面放一下我的思路:
对于问题2,其实就是一个包装组件就解决的事,问题1则比较棘手,在各种搜索引擎加issue都没找到解决的方式,暂时想到了一个比较迂回的方式来处理,见下图:
[外链图片转存失败(img-YyikS5El-1568865122099)(./xxx10.png)]
把所有Route都移到一个包装元素内,当没有组件被渲染时,元素为空,这时就触发了:empty选择器,通过伪类来设置文字提示或者图片提示即可, 这种方式甚至不用依赖js。
2019/9/19更新:
https://github.com/Iixianjie/react-transition-route在新版本的react-transition-route中加入了编程方式处理404的方法
// 处理404
function onPageNotFound(history, url) {
// history.replace('/not-found?url=' + url);
}
<WrapRouter onNotFound={onPageNotFound}>
WrapRouter组件内部通过
import { matchPath } from 'react-router-dom';
来判断是否正确匹配
对于上面的方案,上传了一个开箱即用的组件到npm,它包括如下功能:
- 路由动画由
<Route>
单独管理,更可控并且性能更好(相对于<Switch key={location.key}>{...}</Switch>
,它会在路由切换时整个替换组件树,无论是子路由还是父路由!) - 内置fade、slide两种动画类型,分别用于主次路由切换
- 传递
keepAlive
缓存路由页面 - 路由变更监听,可以通过下面这样的方式传入额外数据,然后通过指定的方法订阅或者在路由组件中接收它们
<Route path="/about" component={About} meta={{name: 'lxj', age: 'xxx'}} />
地址在这里: