Building a wheel is a combination of core principles + peripheral functions, so learning the source code of mature libraries is often interfered by non-core code. The Router repo implements the core mechanism of React Router with less than 100 lines of source code, which is very suitable for learning.
intensive reading
Router quickly implements the 3 core APIs of React Router: Router
, navigate
, Link
, the basic usage is listed below, and it will be more convenient to understand the source code implementation:
const App = () => (
<Router
routes={[
{ path: '/home', component: <Home /> },
{ path: '/articles', component: <Articles /> }
]}
/>
)
const Home = () => (
<div>
home, <Link href="/articles">go articles</Link>,
<span onClick={() => navigate('/details')}>or jump to details</span>
</div>
)
First look at the implementation of Router
, before looking at the code, think about what to do with Router
?
- Receive the routes parameter and determine which component to render based on the current url address.
- When the url address changes (whether triggered by the user or its own
navigate
Link
triggered), the component corresponding to the new url is rendered.
So Router
is a route render dispatcher and url listener:
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
}
The last piece of code seems to be executed every time find
has a certain performance loss, but in fact, according to Router
generally in the root node, this function is rarely triggered by the re-rendering of the parent component Rendering, so performance doesn't have to worry too much.
But if you consider building a complete React Router component library and consider more complex nested APIs, namely Router
set Router
, not only the monitoring method needs to change, but also the hit After the component is cached, the points to be considered will gradually increase.
The following should be implemented navigate
Link
, what they do is jump, there are the following differences:
- The API calling method is different,
navigate
is a calling function, whileLink
is a built-innavigate
capablea
-
Link
In fact, there is also a jump mode that opens a new tab after pressing and holdingctrl
a
So Link
is more complicated, we can reuse it when we first implement navigate
and then implement Link
.
Now Router
has been listening to the popstate
event, we obviously think that after triggering the url change, let popstate
capture it and automatically trigger the subsequent jump logic. But unfortunately, the React Router we are going to do needs to implement single-page jump logic, and the single-page jump API history.pushState
will not trigger popstate
, in order to make the implementation more elegant , we can manually trigger the pushState
popstate
event after ---367376dd2053bc1de16b22067ad31a49---, as shown in the source code:
export function navigate (href) {
// 用 pushState 直接刷新 url,而不触发真正的浏览器跳转
window.history.pushState({}, "", href);
// 手动触发一次 popstate,让 Route 组件监听并触发 onLocationChange
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
}
The next implementation Link
is very simple, there are several considerations:
- Returns a normal
<a>
tag. - Because it is normal
<a>
clicking, the webpage refreshes instead of a single-page jump, so when you click, you need to prevent the default behavior and replace it with oursnavigate
(This abstraction is not made in the source code, the author slightly optimized). - But hold down
ctrl
and open a new tab. At this time, use the default<a>
tab behavior, so do not block the default behavior at this time, and do not continue to executenavigate
, because this url change will not affect the current tab.
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>
);
};
Such a design can not only take into account the default behavior of the preventDefault
metaKey
<a>
tag, but also optimize it to a single-page jump when clicked. study.
Summarize
A few lessons can be learned from this little wheel:
- Before building a wheel, think about how to use the API, and reverse the implementation based on the use of the API, which will make your design more holistic.
- When implementing an API, think about the relationship between APIs first, and design the reuse relationship in advance if it can be reused, so that clever association design can save a lot of trouble for future maintenance.
- Even if the code cannot be reused, try to achieve logical reuse. For example,
pushState
cannot triggerpopstate
that paragraph, directly reuse thepopstate
code, or create a state communication by yourself is too low, use the browser API to simulate Event triggering is both lightweight and logical, because all you have to do is trigger the behavior ofpopstate
, not just update the rendering component, in case there will be monitoring in the futurepopstate
place, your trigger logic can be applied naturally there. - Try to expand on native capabilities instead of using custom methods to complement native capabilities. For example, the implementation of ---ed4b1c35f470d030f8d165814ef79481
Link
is based on the extension of the<a>
tag. If you use a custom<span>
tag, you must not only fill in the difference in stylectrl
but also implement it yourself-ctrl
the behavior of opening a new tab, or even<a>
the default access record behavior, you have to spend a lot of money to make up for it, so the wrong design direction will lead to twice the result with half the effort, or even impossible.
The discussion address is: Intensive reading "react-snippets - Router source code" Issue #418 dt-fe/weekly
If you'd like to join the discussion, click here , there are new topics every week, with a weekend or Monday release. Front-end intensive reading - help you filter reliable content.
Follow Front-end Intensive Reading WeChat Official Account
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Free to reprint - non-commercial - non-derivative - keep attribution ( Creative Commons 3.0 license )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。