紧跟上一篇的步伐, 这一章主要学习react-router-dom的Router
的思想, 和实现自己的Router
组件
一、更新
[2019-4-21]
Fixed
- 修复图片链接失效问题
Changed
- 完善文章格式
二、前言
Router是整个react-router-dom
体系中最重要的一环, BrowserRouter
和HashRouter
都依赖其, Router
组件中做的事情主要有以下几个方面:
- context创建
- 路由监听
三、细说
3.1 context创建
首先看一下Router.js
的源码:
1 | class Router extends React.Component { |
由于Router.js
中用到了RouterContext
这个小东西, 再把目光转向同目录下的RouterContext.js
文件, 大致是这样的:
1 | const context = createNamedContext('Router'); |
通过React
提供的createContext
API, 在RouterContext
中轻松的创建了context
, context
静态函数, 分别是Provider
和Consumer
, 将我们常用的三大API: history
& location
& match
作为value传递给该context的Provider
, 这也是Router
为什么要作为最外层组件的原因.
3.2 路由监听
如果说上面讲的context
是整个react-router-dom
体系的基石的话, 那么history
监听则是整个体系的powered by
, 也就是动力来源
.
先来看一下主要的源码:
1 | this.unlisten = props.history.listen((location) => { |
这里的listen
函数并不稀奇, 这是history
组件官方为开发者提供的钩子函数, 其本质上是对window.onpopstate
做了一层封装. 因此, react应用中的所有url变化
, 反射到Router组件中的监听函数
, 从而将需要被渲染的components
传递给Route
组件, Route
组件通过:
1 | props.path === props.location.pathname |
判断是否渲染该组件…
四、实践
多说无益, 但做无妨. 了解了react-router-dom
的基本思想, 我们继续完善自己的库.
首先, 就是完成我们的Router.tsx
:
1 | // src/yyg-react-router-dom/components/Router.tsx |
接着创建我们的RouterContext
, 值得注意的是:
PS: react-router-dom官方使用的是
create-react-context
这个库, 我们了解一下就行了, 我们可以自己创建.
1 | // src/yyg-react-router-dom/components/RouterContext.tsx |
另外, 值得注意的是:
PS: 在书写
.tsx
的时候, 最好将公共的types定义提取出来, 这样将可以将上述match
对象的接口定义在types.tsx
中, 方便后续融合到*.d.ts
中发布…
1 | // src/yyg-react-router-dom/types.tsx |
基本上到这里, Router组件的相关内容已经搞定了, 下面该做一下测试来验证我们的组件..
五、测试
实践完自己的react-router-dom
库之后, 需要作一个简单的测试, 验证是否能预期工作…
这里, 由于Route组件需要直接使用context.consumer
, 所以, 我们就地取材, 使用同目录下的Route.tsx
来test, 在Route
组件中写入我们自己的测试内容:
1 | // src/yyg-react-router-dom/components/Route.tsx |
完成之后, 在App.tsx
中引入我们的Route组件, 像这样:
1 | // src/App.tsx |
完成上述步骤之后, 我们npm start
启动服务器, console栏中可以看到如下输出:
我们可以看到, 这里输出了一个空对象, 当然这也就是我们为RouterContext
设置的默认值了. 出现这一步, 代表我们已经打通了context
传递的流程. 接下来, 我们还得测试url的变化, 是否能反映到Router中的context, 然后又能否通过context的Provider
分发给对应的Consumer
这里, 有个小方法, 我们可以将history
对象挂在到window
上, 这样可以方便的在chrome中调试了. 打开BrowserRouter.tsx
组件, 在创建好browserHistory
对象之后, 同时将其挂载到window
上:
1 | // src/yyg-react-router-dom/components/BrowserRouter.tsx |
接着, 打开chrome的console栏, 输入browserRouter
, 出现如图所示就代表已经成功了…
然后, 命令行中输入browserRouter.push('/duan')
, 可以看到url地址栏已经变化了, 但是….. console栏没有输出我们的Route.tsx
中的context
, 按理说, 我们已经在Router
中监听了url的变化, 而这种变化会即时反映到Context.Provider
中, 但是这里并不是我们预期的那样…原因在哪里呢???
带着这个问题, 我们再次打开App.tsx
, 我们发现, 此时的
Route
和BrowserRouter
并不是父子关系, 也就是说Consume
和Provider
是分开的, 这也是问题所在. 将<Route>
组件放置在<BrowserRouter>内
. 保存打开chrome, console中输入browserRouter.push('/zhaoyang')
, 这时候, 可以清楚的看到:
每一次url的变化, Route
组件都能正常的接收到这种改变, 到这里, 我们的测试步骤就完成了. 当然, 还有一些错误处理, 后续可以再完善…
六、源码
源码地址: 点我
七、总结
这一篇主要学习了Router
组件的工作流程, 下一篇将会看看Route
, 同样也是非常重要的一个组件. 最后, 附上一张流程图, 对这一节作一个总结: