谁说 fre 没有 router? 100 行代码实现 router

如题,大家好久不贱,这是一篇关于前端路由的文章

前情提要

前端路由的跳转有两种

预加载模式:一种是点击一个按钮,显示loading条,等全都准备好了,再跳过去,比如github就是这种,这在wordpress+htmx的世界里也比较常见,也就是先加载,后跳转

即时跳转模式:另一种是点击按钮就立即跳转,然后数据没准备好就展示一个loading按钮,这种在国内的 spa 中比较常见,也就是先跳转,后加载

知道这一点,我们对路由跳转就大概有数了,我们今天就来探讨这两种跳转模式的结合方式

其中即时跳转模式,可以通过 useEffect(数据) + Suepense(lazy组件) 实现,而预加载模式,通常在 react 的语境里,需要使用类似 startTransition 等实现

remix loader api

remix 提供了一种新的思路,就是给每一个路由提供一个 loader 函数,我们可以在路由中先请求 loader 函数,再渲染组件,以此来做到"预加载模式"

好的,复习结束,接下来上代码

实现

js 复制代码
const routes = [
    {
        path: '/login',
        element: import('./login/login'), // without loader
    },
    {
        path: '/',
        element: import('./home/home'), 
        loader: homeLoader // loader
    },
    {
        path: '/uu/:id',
        element: import('./user/user'),
        loader: userLoader
    },
]

const App = () => {
    return <main>
        <Router routes={routes}/>
    </main>
}

如上,login 组件没有 loader,那就立即跳转,而 home 和 user 组件有 loader,则需要先预加载,再跳转

fre-router 最终代码

js 复制代码
import { useState, useEffect, Suspense, lazy, useMemo, useRef } from 'fre'

function pathSlice(path) {
  const slice = [
    new RegExp(
      `${path.substr(0, 1) === '*' ? '' : '^'}${path
        .replace(/:[a-zA-Z]+/g, '([^/]+)')
        .replace(/\*/g, '')}${path.substr(-1) === '*' ? '' : '$'}`
    )
  ]
  const params = path.match(/:[a-zA-Z#0-9]+/g)
  slice.push(params ? params.map(name => name.substr(1)) : [])
  return slice
}

export function useRouter(routes) {
  const [url, setUrl] = useState(window.location.pathname)

  const parsedRoutes = useMemo(() => {
    return routes.map(item => ({
      route: pathSlice(item.path),
      handler: lazy(() => item.element),
      loader: item.loader
    }))
  }, [routes])

  useEffect(() => {
    const handlePopState = () => {
      setUrl(window.location.pathname)
    }
    window.addEventListener('popstate', handlePopState)
    return () => window.removeEventListener('popstate', handlePopState)
  }, [])

  return { url, routes: parsedRoutes }
}

export default function Router({ routes, fallback }) {
  const { url, routes: parsedRoutes } = useRouter(routes)
  const [match, setMatch] = useState(null)
  const [pending, setPending] = useState(false)
  const requestId = useRef(0)

  useEffect(() => {
    const currentUrl = url === '' ? '/' : url
    const currentRequestId = ++requestId.current

    const matchedRoute = parsedRoutes.find(({ route }) => {
      const [reg] = route
      return currentUrl.match(reg)
    })

    if (!matchedRoute) {
        setMatch(null)
        setPending(false)
      return
    }

    const [reg, params] = matchedRoute.route
    const res = currentUrl.match(reg)
    const newProps = {}
    if (params.length > 0 && res) {
      params.forEach((prop, index) => (newProps[prop] = res[index + 1]))
    }

    const loadRoute = async () => {

      if (currentRequestId !== requestId.current) return

      setPending(true)
      
      let data = null

      if (typeof matchedRoute.loader === 'function') {
        try {
          data = await matchedRoute.loader(newProps)
        } catch (error) {
          console.error('Loader error:', error)
        }
      }

      if (currentRequestId === requestId.current) {
        setMatch({
          Component: matchedRoute.handler,
          props: { data, ...newProps },
        })
        setPending(false)
      }
    }

    loadRoute()
  }, [url, parsedRoutes])

  return (
    <Suspense fallback={fallback}>
      {pending && fallback}
      {match && <match.Component {...match.props} />}
    </Suspense>
  )
}

export const push = (path) => {
  if (path.startsWith('.')) {
    path = window.location.pathname + path.slice(1)
  }
  if (window.location.pathname !== path) {
    history.pushState({}, '', path)
    window.dispatchEvent(new PopStateEvent('popstate'))
  }
}

export const getPath = () => window.location.pathname

最终效果:www.ixipi.net

大家可以自己跳转试试看,效果还是很赞的,虽然是 fre 的代码,但 react 同样适用

github.com/frejs/fre

俺现在有书读了,fre 最近也更新了一些,支持了 Suspense 和 lazy,大家感兴趣可以点个 star

相关推荐
芒果茶叶31 分钟前
并行SSR,SSR并行加载
前端·javascript·架构
vortex544 分钟前
解决 Kali 中 Firefox 下载语言包和插件速度慢的问题:配置国内镜像加速
前端·firefox·腾讯云
修仙的人1 小时前
Rust + WebAssembly 实战!别再听说,学会使用!
前端·rust
maxine1 小时前
JS Entry和 HTML Entry
前端
用户63310776123661 小时前
Who is a Promise?
前端
比老马还六1 小时前
Blockly元组积木开发
前端
笨笨狗吞噬者2 小时前
【uniapp】小程序体积优化,JSON文件压缩
前端·微信小程序·uni-app
西洼工作室2 小时前
浏览器事件循环与内存管理可视化
前端·javascript·css·css3
xier1234562 小时前
高性能和高灵活度的react表格组件
前端
你打不到我呢2 小时前
nestjs入门:上手数据库与prisma
前端