核心API
- Router
- navigate
-
Router
看看使用起来的 api:
const App = () => (
<Router
routes={[
{ path: '/home', component: <Home /> },
{ path: '/articles', component: <Articles /> }
]}
/>
)
接收了 routes 参数,是一个数组,数组中每个对象中有url路径和对应要渲染的组件
-
实现
export default function Router ({ routes }) {
// 存储当前 url path,方便其变化时引发自身重渲染,以返回新的 url 对应的组件
const [currentPath, setCurrentPath] = useState(window.location.pathname);
useEffect(() => {
const onLocationChange = () => {
// 将 url path 更新到当前数据流中,触发自身重渲染
setCurrentPath(window.location.pathname);
}
// 监听 popstate 事件,该事件由用户点击浏览器前进/后退时触发
window.addEventListener('popstate', onLocationChange);
return () => window.removeEventListener('popstate', onLocationChange)
}, [])
// 找到匹配当前 url 路径的组件并渲染
return routes.find(({ path, component }) => path === currentPath)?.component
}
和真实的差距:
没法 router 嵌套
跳转
不同:
- API:
- navigate 是调用式函数
- Link :一个内置 navigate 的 a标签
实现
export function navigate (href) {
// 用 pushState 直接刷新 url,而不触发真正的浏览器跳转
window.history.pushState({}, "", href);
// 手动触发一次 popstate,让 Route 组件监听并触发 onLocationChange
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
}
刷新 url 后 手动触发 popstate 事件让 Router 控制跳转
Link
- 首先 Link 本身是一个 a 标签
- 正常 a 标签点击后会刷新页面而不是单页跳转,所以要先组织 刷新页面这个默认行为
-
实现
export function Link ({ className, href, children }) {
const onClick = (event) => {
// mac 的 meta or windows 的 ctrl 都会打开新 tab
// 所以此时不做定制处理,直接 return 用原生行为即可
if (event.metaKey || event.ctrlKey) {
return;
}
// 否则禁用原生跳转
event.preventDefault();
// 做一次单页跳转
navigate(href)
};
return (
<a className={className} href={href} onClick={onClick}>
{children}
</a>
);
};
参考