来,手写一个简单的路由(二)

165 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

前言

前面我们讲到如何封装一个基本的路由,实现页面刷新能够显示相应的组件。

但同时我们也面对一个问题,就是在浏览器的前进后退操作中,我们并不能响应相关的刷新操作,如果通过监听前进后退对页面进行强制刷新,其实也是可以实现前进后退显示相应的组件,同时Link组件通过强制刷新或者a链接跳转也可以实现我们想要的效果。

我们知道,页面的重新请求与局部刷新,前者的性能消耗是非常高的,因为多了多余的页面渲染和重复的接口请求,而局部刷新可以在相应的effect里执行少数的接口请求以及少数的页面渲染操作。

对于前进后退以及新push页面的操作。在前面的文章实验中我们有所了解,这里就不做过多的阐述。

言归正传,现在我们要做的是实现前进后退不刷新页面,只需要触发局部更新以及新push地址渲染相应的组件。

路由事件监听

前进后退的监听

浏览器的地址栈模型中,前进与后退称为pop操作。(小声逼逼,然而并没有区分前进与后退相应的api)

截屏2022-06-02 下午10.40.23.png

对于监听前进与后退的操作,其事件命名为popstate,所以我们可以在路由里执行effect操作。在退出页面的时候清除监听操作。

function MyRouter(props) {
  //基于window的location,我们匹配当前的符合选项
  //实际上在react-router是通过switch去匹配符合项,我们也可以通过是否精确匹配处理匹配规则还有地址栏参数等
  const { pathname } = window.location
  const [historyRender,setHistoryRender] =  useState(false)
  let Comp = props.route.find(item => item.pathname === pathname)
  useEffect(()=>{
    const handlePopHistory = ()=>{
      //这里重新读取pathname是因为上面的pathname是旧值,页面更改并不会导致上面的pathname重新赋值。
      const { pathname } = window.location
      setHistoryRender(pathname);
    }
    window.addEventListener('popstate',handlePopHistory)

    return ()=>{
      //在这里页面退出时取消监听
      window.removeEventListener('popstate',handlePopHistory)
    }
  },[])
  
   useEffect(()=>{
    Comp = props.route.find(item => item.pathname === pathname)
  },[historyRender])

  return <>
    {Comp.component}
  </>
}

上面的代码主要是监听pop操作,通过setState执行页面刷新操作,而页面刷新的依赖则由页面的pathname决定。

push操作的监听

在地址栏新开地址的操作成为push,通过window.history.pushState这个方法去操作。而我们只需要在执行该操作的时候顺便手动刷新页面即可。

function MyLink({meta={},title='未命名',to='/'}) {
  const handleLinkClick = () => {
        window.history.pushState(meta,title,to)
  }
  return <>
    <span onClick={handleLinkClick}>  {props.children}</span>
  </>
}

此时我们发现我们并不能去控制MyRouter组件里状态的改变。

而我们又想实现push操作的监听,所以我们可以通过自定义事件去操作。

const event = new CustomEvent('myEvent')
window.dispatchEvent(event);//派发全局事件
//全局监听事件进行操作
window.addEventListener('myEvent', ()=>{
});

我们在MyRoute里自定义pushHistory事件:

function MyRouter(props) {
  //基于window的location,我们匹配当前的符合选项
  //实际上在react-router是通过switch去匹配符合项,我们也可以通过是否精确匹配处理匹配规则还有地址栏参数等
  const { pathname } = window.location
  const [historyRender, setHistoryRender] = useState(false)
  let Comp = props.route.find(item => item.pathname === pathname)
  useEffect(() => {
    //更改函数命名
    const handleHistoryChange = () => {
      const { pathname } = window.location
      setHistoryRender(pathname);
    }
    window.addEventListener('popstate', handleHistoryChange)
    //监听自定义事件
    window.addEventListener('pushHistory', handleHistoryChange)

    return () => {
      window.removeEventListener('popstate', handleHistoryChange)
      window.addEventListener('pushHistory', handleHistoryChange)
    }
  }, [])

  useEffect(() => {
    Comp = props.route.find(item => item.pathname === pathname)
    console.log(Comp)
  }, [historyRender])

  return <>
    {Comp.component}
  </>
}

在push的行为上主动触发事件:

function MyLink({ meta = {}, title = '未命名', to = '/', children }) {
  const handleLinkClick = () => {
    const event = new CustomEvent('pushHistory')
    window.history.pushState(meta, title, to)
    window.dispatchEvent(event)
  }
  return <>
    <span onClick={handleLinkClick}>  {children}</span>
  </>
}

从上面的代码中,我们就实现了页面前进后退以及push操作的组件渲染啦。

主页:

截屏2022-06-02 下午11.30.53.png

点击进入home页面:

截屏2022-06-02 下午11.31.59.png

点击进入about页面:

截屏2022-06-02 下午11.32.25.png

回退:

截屏2022-06-02 下午11.33.45.png

为此我们实现了简单的路由,根据地址实现组件的渲染。

小结

组件的渲染在内部需要手动setState,而我们主要通过popState事件去执行setState操作,而push的主动触发主要是触发自定义事件实现setState操作。

组件即对象,而路由则是通过匹配规则去匹配对应的对象~