X

React Router V5 升级v6

2022/5/24

最近在给项目框架依赖做升级

修bug顺便把React16升到18 简单测试了下没啥问题就交付了

然后今天甲方那边测试反馈回退按钮不好用 顿时感觉微妙

我测试了下 确实 第一次好使 再次点击就没反应了

看看控制台 它也没有报错 什么输出都没有

回退是有触发路由变化的 但是页面没有渲染

给路由加断点 甚至没有进到组件渲染

行吧 干脆路由也升级一下?

于是 react router 从v5 切到最新

app直接白屏

白屏没关系 至少有报错输出

v6的文档感觉不怎么友好 查问题很是费力

简单列一下踩的坑:

1.basename 没生效

之前

createBrowserHistory({
    basename: '/h5'
});

就能保证路径前面自动加上h5

解决方法:配置Router的location

Route的history已经没有了 取而代之是更加麻烦的写法

感觉不怎么优雅

const [state, setState] = React.useState({
    action: history.action,
    location: history.location,
});

React.useLayoutEffect(() => history.listen((s)=>{
    const ss = {...s}
    ss.location={...s.location,pathname: '/h5'+s.location.pathname.replace(/^\/h5/,'')}
    setState(ss)
}), [history]);

<Router
   basename={'/h5'}
   location={state.location}
   navigationType={state.action}
   navigator={history}
>

history.liste这里监听变化 然后改变地址

2.Route没有了render

现在Route只能这么写

<Route path="/xxx/*" element={<YourElement/>}/>

看上去简洁 但是我原有的render里面有更加灵活的内容 它这么一遍 我又得改变组件的包装

adapter我之前的路由包装方法

 const routes = Object.keys(rs).map(adapter);

现在 我们需要把以前一些从路由上传入的props手动在包装器里加入

  const adapter = (c, i) => {
        const cp = rs[c];
        const cpm = lzK(c);
        const Element = () => {
            const E = props => {
                return <Cpm  {...props} cpm={cpm} c={c}/>
            }
            const  match = useMatch(c);
            return <E  match={match}/>
        }
        return <Route
            key={'r-' + i}
            path={'/' + c + (cp.exact ? '' : '/*')}
            element={<Element/>}
        />
    };

大概看了下 以前只用到了其中的 match

虽然有包装mobx的store,不过感谢自己当初写Observe外层包装时候直接使用store而不是从父组件传入store,不然还得想办法在这里传入store

这个好说:

<NavLink className='c0' activeClassName='act'.../>

现在 NavLink的className可以接入方法({isActive})=>String

所以包装下即可

const actCls =(c0, c1='act') => ({isActive}) => c0 + (isActive ?(' ' + c1): '' )
 <NavLink className={actCls('c0')}.../>

4.其它

Switch 变为Routes

Redirect 变为 Navigate

这些都是名字变化 替换就好

和Router无关的一些

history js 不知道什么时候移除了length属性

以前我是根据它来判断回退是否到了最初页面避免返回到更早的页面去了

所以要加监听 记录历史页面深度

let length = +window.sessionStorage.hisLength||0

h.listen(({action})=>{
    if(action==='PUSH')length++
    else if(action==='POP')length++
    window.sessionStorage.hisLength = length
})

h.back = () => {
    if (length>1) h.go(-1);
    else h.replace('/h5')
}

今日份另外一个报错是React Lazy方法的报错

说返回了Object

这个又是断点断个寂寞的报错

我的项目有去主动加载按需加载的组件 来节约加载时间

也没有具体去看lazy里面具体在做什么

直觉就是能返回一个和import('./xxxx.js')结构差不多的Promise就行

最后resolve里构造一个{default:xxxx}结构的对象才顺利解决问题

无技巧 就是凭经验猜谜~

function preLoad(key) {
    const nx = lzP[key];
    if (nx) {
        return new Promise((r) => nx.then(a => {
            delete lzP[key];
            r(a);
        }))
    } else return lzP[key] = new Promise((resolve) => {
        const cp = rs[key];
        const [p, k] = cp.component || cp;
        p().then(a => {
            if (k) {
                a = a[k]
            }
            const C = a&&a.default||a
            lzC[key] = props=><C {...props}/>;
            resolve({default:lzC[key]});
        })
    })
}

function lzK(key) {
    const c = lzC[key];
    if (c) return c;
    else return React.lazy(() => preLoad(key))
}
Commit