Happy New Year!

一、更新


[2019-4-21]

Changed

  • 改进文章格式

二、前言


给自己拜个早年, 祝自己在2019年:

  • 前端技术大有长进
  • 吃饭多多
  • 找个好工作

过年好, 疯狂暗示

早上7点起来, 朦朦胧胧, 坐在电脑桌前, 一脸懵逼…

想了想, 今天还是继续之前的源码学习吧, 上一次学习了Switch组件, 今天主要是来看一下Link.

三、细说


Link组件被放置在packages下的react-router-dom包中, 与react-router做了隔离,

PS: 虽然不知道作者这么做的意图, 但是佩服就对了

但这些都是次要的, 打开Link.js文件, 预览一下大致的结构, 可以看到, 源码行数与Switch并无两样, 仔细看, Link类中, 其实只有一个handleClick处理方法. 所以, 按理说, 并不应该很难理解:

疯狂暗示_动图

正式开始分析之前, 先来回想一下使用思路? 平时用到最多的可能就是Link了, 一般都是做Menu点击跳转之类的, 与之功能类似的有一个history.push, 两者功能差不多. 与Link对应的还有一个NavLink, 可以自定义active样式, 这个下一篇再说吧.

PS: Link点击跳转, url改变, 对应模块展现, 一气呵成.

带着这个想法, 开始阅读源码:

首先, 可以看到:

1
2
3
4
5
return (
<RouterContext.Consumer>
{...}
<RouterContext.Consumer>
);

还是熟悉的做法, 接收context做内部操作.

接着:

1
2
3
4
5
const location =
typeof to === "string"
? createLocation(to, null, null, context.location)
: to;
const href = location ? context.history.createHref(location) : "";

分为两步

  • 计算location
  • 根据location计算href

根据props传递的to参数, 如果是string, 则调用history.push直接跳转, 如果是object, 则使用history.createHref进行包装, 并返回相应的path.

接着, 再往下看:

1
2
3
4
5
6
7
return (
<a
...
onClick={event => this.handleClick(event, context.history)}
...
>
);

Link的核心处理函数, vscodeCtrl+MouseLeft直接进入handleClick函数内部, 可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
handleClick(event, history) {
if (this.props.onClick) this.props.onClick(event);

if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();

const method = this.props.replace ? history.replace : history.push;

method(this.props.to);
}
}

这是源码所示, 下面将相关注解直接挂载到对应源码上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
handleClick(event, history) {
if (this.props.onClick) this.props.onClick(event);

if (
// ** 如果默认事件已被阻止 **
// ** 避免innerRef直接操作a链接 **
!event.defaultPrevented &&
// ** 只允许鼠标左键点击 **
event.button === 0 &&
// ** 排除当target=_blank时, 打开新页面, 破坏单页原则 **
(!this.props.target || this.props.target === "_self") &&
// ** 避免使用键盘快捷键+鼠标的操作 **
// ** 形如 Ctrl + MouseLeft, 会在新页面打开链接 **
!isModifiedEvent(event)
) {
event.preventDefault();

// ** 通过 replace 判断是否将当前location替换为新的location **
// ** 避免堆栈 **
const method = this.props.replace ? history.replace : history.push;

method(this.props.to);
}
}

执行完这一步操作, 基本上Link的核心已经学习完毕了, 感觉很简单, 这也许是整个react-router-dom体系中最简单的一个(哦, 还有下一篇将要分析的NavLink).

PS: 多说不做非君子

下面, 接着来完善自己的yyg-react-router-dom库.

四、实践


这里只写个大概, 完整源码参考: 这里

4.1 定义interface

PS: 非常重要的一步, 本能反应

打开react-router官网, 可以看到Link的所需props, 🆗, 现在来定义它:

1
2
3
4
5
6
7
8
9
// src/yyg-react-router-dom/components/Link.tsx
export interface ILinkProps {
to?: string | LocationDescriptorObject<{
[key: string]: any,
}>;
replace?: boolean;
innerRef?: (node: React.Ref<HTMLLinkElement>) => void;
children?: React.ReactChildren;
};

接着, 按照以前的分布策略, 将处理context的函数提取出来, 再分别进行不同的处理操作, 大致结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
// src/yyg-react-router-dom/components/Link.tsx
function handleProcess(
context: IStaticRouterComponentParams,
): JSX.Element {
// ** do any **
}

<RouterContext.Consumer>
{
(context) => handleProcess(context as IStaticRouterComponentParams)
}
<RouterContext.Consumer>

这里只是再三提醒自己注意代码规范, 俗话说:

PS: 好记性不如烂笔头

做的多了, 自然而然就熟练了.

接着, 在handleProcess函数中, 要进行的则是计算href, 以及处理aonClick, 所以按照之前的思路, 根据props.to的值进行location的计算, 得到createHref的返回值, 这个返回值就是path, 所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/yyg-react-router-dom/components/Link.tsx
...
const href = (
to
? _isString(to)
? context.history.createHref(createLocation(to, null, '', context.location))
: context.history.createHref(to as LocationDescriptorObject<{
[key: string]: any,
}>)
: ''
) as string;

return (
<a
{...others}
href={href}
onClick={(e) => handleLinkClick(e, context, href, replace)}
>{children}</a>
);

...

计算完href, 就要处理click了, 将其提取至两一个function, 并传入所需的参数:

1
2
3
4
5
6
7
8
function handleLinkClick(
e: React.MouseEvent<HTMLAnchorElement>,
context: IStaticRouteComponentParams,
href: string,
replace?: boolean,
): void {
// ** 省略n个字 **
}

最后, 做整合, 优化一下代码, 开始测试环节

五、测试


简单的写完自己的代码, 简单起见, 对toreplace这两个常用参数作测试:

App.tsx中, 修改测试路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/App.tsx
return (
<div>
<BrowserRouter>
<Switch>
<Route
path={"/test"}
render={() => (
<Switch>
<Route exact path="/test" component={One} />
<Route exact path="/test/home" component={Three} />
<Route exact path="/test/product" component={Four} />
</Switch>
)}
/>
</Switch>
</BrowserRouter>
</div>
);

进入到One.tsx中, 添加一些内容, 当然是Link组件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/test/One.tsx
return (
<div>
<h1>Page One</h1>
<ul>
<li>
<Link
to="/test/home"
>Home</Link>
</li>
<li>
<Link
to={{
pathname: '/test/product',
state: {
id: '19980808',
name: 'duan',
},
}}
>Product</Link>
</li>
</ul>
</div>
);

测试代码并不是固定的, 自己随意测试即可…

之后, 打开CentBrowser, 可以看到:

entbrowser测试

通过点击使用Link组件, 浏览器url可以正常改变, 并且对应的组件可以正常渲染, 充分证明我们自己封装的Link没有问题.

六、源码


完整源码已上传至 Gayhub

七、总结


最后, 以一张脑图来结束今天的学习:

思维脑图