场景
h5 开发经常有个场景,有两个和多个页面相互跳转。比如列表跳转详情,详情返回列表需要保存列表原来状态,如查询参数 列表滚动位置等等。react-router
每当路由更改的时候都会销毁上一次的组件,内部的状态都会被销毁。实现以上的需要可能需要类似 redux
状态的持久化的组件库,
而且实现上也比较繁琐,需要根据跳转的页面去判断是否状态清空/保存。
在 react-router
的基础上实现类似 vue-router
的 keep-alive
功能尼?
是否可以在一个路由下实现类似的路由机制尼?
初步实现
简版实现,需在实践中完善,仅供大家参考
- 内部路由的存储
- push 功能
- goBack 功能
- replace 功能
- 路由跳转的参数传递
class RouteStack {
constructor(initRoute, state) {
if (initRoute, state) {
// 当前路由栈
this.stack = [{ path: initRoute, state }]
// 当前路由
this.currentRoute = initRoute
}
}
push(path, state) {
if (!this.stack.some(ele => ele.path == path)) {
this.stack.push({ path, state })
}
this.currentRoute = path
}
replace(path, state) {
this.stack = [{ path, state }]
this.currentRoute = path
}
goBack(length = 1) {
this.stack = this.stack.slice(0, 0 - length)
if (this.stack.length) {
this.currentRoute = this.stack[this.stack.length - 1].path
}
}
}
使用一个数组模拟路由跳转的过程, 内部存储 stack
表示当前的栈。 push
goBack
replace
等方式更改路由和路由栈。state
则是跳转到当前的参数。
与 react
结合:
function funWrapper(obj, a, b) {
return (...args) => {
a.bind(obj)(...args)
b()
}
}
const pureObject = {}
const useUpdate = () => {
const [, setState] = useState(0);
return useCallback(() => setState(pre => pre++), []);
};
export default function MockRoute(props) {
const { config, initRoute, ...restProps } = props
const forceUpdate = useUpdate()
const routeStack = useRef(null)
useEffect(() => {
routeStack.current = new RouteStack(initRoute)
forceUpdate()
}, [])
const history = useMemo(() => {
if (routeStack.current === null) {
return null
}
return {
...routeStack.current,
goBack: funWrapper(routeStack.current, routeStack.current.goBack, forceUpdate),
push: funWrapper(routeStack.current, routeStack.current.push, forceUpdate),
replace: funWrapper(routeStack.current, routeStack.current.replace, forceUpdate),
}
}, [routeStack.current])
if (!history) {
return null
}
const { stack, currentRoute } = routeStack.current
return (
<Fragment>
{stack.map(ele => {
const { state = pureObject, path } = ele
const Component = (config.find(it => it.path === path) || {}).component
if (!Component) {
return null
}
const style = {}
if (path !== currentRoute) {
style.display = 'none'
}
return (
<div className="mock-router-wrapper" style={style}>
<Component {...restProps} mockHistory={history} params={state} key={path} />
</div>
)
})}
</Fragment>
)
}
- 实例化
RouteStack
挂载到ref
上 - 使用
useUpdate
更新路由渲染 - 根据当前的
stack
currentRoute
渲染组件, 并将对应的params/state
传入,并传入一个mockHistory
对象 - 不过引进了一个
div
做展示的切换,可能会影响布局。
使用
const routeConfig = [
{
path: 'detail',
component: Loadable({
loader: () => import('../detail'),
}),
},
{
path: 'list',
component: Loadable({
loader: () => import('../list'),
}),
},
]
function Wrapper(props) {
const { match, ...restProps } = props
const { initPage, direction } = match.params
return (
<MockRoute
initRoute={initPage}
config={routeConfig}
/>
)
}
const dataSource = [
{ key: '1', name: '1' }
{ key: '2', name: '2' }
{ key: '3', name: '3' }
]
function Detail(props) {
const { mockHistory, params } = props
return (
<div>
<span>{params.name}</span>
<button onClick={() => { mockHistory.goBack() }}>返回</button>
</div>
)
}
function List(props) {
const { mockHistory } = props
return (
<div>
{dataSource.map(ele => (
<div
onClick={() => { mockHistory.push('detail', { name: ele.name }) }}
>
{ele.name}
</div>
))}
</div>
)
}
TODO
- 监听返回操作
- 完善
goBack
push
replace
总结
这个组件在实现中,大家可以给点意见/需求 完善这个需求。本文持续更新中。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。