持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
前言
前面我们讲到如何封装一个基本的路由,实现页面刷新能够显示相应的组件。
但同时我们也面对一个问题,就是在浏览器的前进后退操作中,我们并不能响应相关的刷新操作,如果通过监听前进后退对页面进行强制刷新,其实也是可以实现前进后退显示相应的组件,同时Link组件通过强制刷新或者a链接跳转也可以实现我们想要的效果。
我们知道,页面的重新请求与局部刷新,前者的性能消耗是非常高的,因为多了多余的页面渲染和重复的接口请求,而局部刷新可以在相应的effect里执行少数的接口请求以及少数的页面渲染操作。
对于前进后退以及新push页面的操作。在前面的文章实验中我们有所了解,这里就不做过多的阐述。
言归正传,现在我们要做的是实现前进后退不刷新页面,只需要触发局部更新以及新push地址渲染相应的组件。
路由事件监听
前进后退的监听
浏览器的地址栈模型中,前进与后退称为pop操作。(小声逼逼,然而并没有区分前进与后退相应的api)
对于监听前进与后退的操作,其事件命名为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操作的组件渲染啦。
主页:
点击进入home页面:
点击进入about页面:
回退:
为此我们实现了简单的路由,根据地址实现组件的渲染。
小结
组件的渲染在内部需要手动setState,而我们主要通过popState事件去执行setState操作,而push的主动触发主要是触发自定义事件实现setState操作。
组件即对象,而路由则是通过匹配规则去匹配对应的对象~