场景

h5 开发经常有个场景,有两个和多个页面相互跳转。比如列表跳转详情,详情返回列表需要保存列表原来状态,如查询参数 列表滚动位置等等。
react-router 每当路由更改的时候都会销毁上一次的组件,内部的状态都会被销毁。实现以上的需要可能需要类似 redux 状态的持久化的组件库,
而且实现上也比较繁琐,需要根据跳转的页面去判断是否状态清空/保存。

react-router 的基础上实现类似 vue-routerkeep-alive 功能尼?

是否可以在一个路由下实现类似的路由机制尼?

初步实现

简版实现,需在实践中完善,仅供大家参考

  1. 内部路由的存储
  2. push 功能
  3. goBack 功能
  4. replace 功能
  5. 路由跳转的参数传递
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

总结

这个组件在实现中,大家可以给点意见/需求 完善这个需求。本文持续更新中。。。


fall_wind
82 声望9 粉丝

九层之台 起于垒土