- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章ReactRouter的实现方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
ReactRouter是React的核心组件,主要是作为React的路由管理器,保持UI与URL同步,其拥有简单的API与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理等.
React Router是建立在history对象之上的,简而言之一个history对象知道如何去监听浏览器地址栏的变化,并解析这个URL转化为location对象,然后router使用它匹配到路由,最后正确地渲染对应的组件,常用的history有三种形式: Browser History、Hash History、Memory History.
Browser History是使用React Router的应用推荐的history,其使用浏览器中的History对象的pushState、replaceState等API以及popstate事件等来处理URL,其能够创建一个像https://www.example.com/path这样真实的URL,同样在页面跳转时无须重新加载页面,当然也不会对于服务端进行请求,当然对于history模式仍然是需要后端的配置支持,用以支持非首页的请求以及刷新时后端返回的资源,由于应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问URL时就会返回404,所以需要在服务端增加一个覆盖所有情况的候选资源,如果URL匹配不到任何静态资源时,则应该返回同一个index.html应用依赖页面,例如在Nginx下的配置.
1
2
3
|
location / {
try_files $uri $uri/ /index.html;
}
|
Hash符号即#原本的目的是用来指示URL中指示网页中的位置,例如https://www.example.com/index.html#print即代表example的index.html的print位置,浏览器读取这个URL后,会自动将print位置滚动至可视区域,通常使用<a>标签的name属性或者<div>标签的id属性指定锚点。 通过window.location.hash属性能够读取锚点位置,可以为Hash的改变添加hashchange监听事件,每一次改变Hash,都会在浏览器的访问历史中增加一个记录,此外Hash虽然出现在URL中,但不会被包括在HTTP请求中,即#及之后的字符不会被发送到服务端进行资源或数据的请求,其是用来指导浏览器动作的,对服务器端没有效果,因此改变Hash不会重新加载页面。 ReactRouter的作用就是通过改变URL,在不重新请求页面的情况下,更新页面视图,从而动态加载与销毁组件,简单的说就是,虽然地址栏的地址改变了,但是并不是一个全新的页面,而是之前的页面某些部分进行了修改,这也是SPA单页应用的特点,其所有的活动局限于一个Web页面中,非懒加载的页面仅在该Web页面初始化时加载相应的HTML、JavaScript、CSS文件,一旦页面加载完成,SPA不会进行页面的重新加载或跳转,而是利用JavaScript动态的变换HTML,默认Hash模式是通过锚点实现路由以及控制组件的显示与隐藏来实现类似于页面跳转的交互.
Memory History不会在地址栏被操作或读取,这就可以解释如何实现服务器渲染的,同时其也非常适合测试和其他的渲染环境例如React Native,和另外两种History的一点不同是我们必须创建它,这种方式便于测试.
1
|
const history = createMemoryHistory(location);
|
我们来实现一个非常简单的Browser History模式与Hash History模式的实现,因为H5的pushState方法不能在本地文件协议file://运行,所以运行起来需要搭建一个http://环境,使用webpack、Nginx、Apache等都可以,回到Browser History模式路由,能够实现history路由跳转不刷新页面得益与H5提供的pushState()、replaceState()等方法以及popstate等事件,这些方法都是也可以改变路由路径,但不作页面跳转,当然如果在后端不配置好的情况下路由改编后刷新页面会提示404,对于Hash History模式,我们的实现思路相似,主要在于没有使用pushState等H5的API,以及监听事件不同,通过监听其hashchange事件的变化,然后拿到对应的location.hash更新对应的视图.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
<!-- Browser History -->
<!DOCTYPE html>
<html lang=
"en"
>
<head>
<meta charset=
"UTF-8"
>
<title>Router</title>
</head>
<body>
<ul>
<li><a href=
"/home"
rel=
"external nofollow"
>home</a></li>
<li><a href=
"/about"
rel=
"external nofollow"
>about</a></li>
<div id=
"routeView"
></div>
</ul>
</body>
<script>
function
Router() {
this
.routeView =
null
;
// 组件承载的视图容器
this
.routes = Object.create(
null
);
// 定义的路由
}
// 绑定路由匹配后事件
Router.prototype.route =
function
(path, callback) {
this
.routes[path] = () =>
this
.routeView.innerHTML = callback() ||
""
;
};
// 初始化
Router.prototype.init =
function
(root, rootView) {
this
.routeView = rootView;
// 指定承载视图容器
this
.refresh();
// 初始化即刷新视图
root.addEventListener(
"click"
, (e) => {
// 事件委托到root
if
(e.target.nodeName ===
"A"
) {
e.preventDefault();
history.pushState(
null
,
""
, e.target.getAttribute(
"href"
));
this
.refresh();
// 触发即刷新视图
}
})
// 监听用户点击后退与前进
// pushState与replaceState不会触发popstate事件
window.addEventListener(
"popstate"
,
this
.refresh.bind(
this
),
false
);
};
// 刷新视图
Router.prototype.refresh =
function
() {
let path = location.pathname;
console.log(
"refresh"
, path);
if
(
this
.routes[path])
this
.routes[path]();
else
this
.routeView.innerHTML =
""
;
};
window.Router =
new
Router();
Router.route(
"/home"
,
function
() {
return
"home"
;
});
Router.route(
"/about"
,
function
() {
return
"about"
;
});
Router.init(document, document.getElementById(
"routeView"
));
</script>
</html>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<!-- Hash History -->
<!DOCTYPE html>
<html lang=
"en"
>
<head>
<meta charset=
"UTF-8"
>
<title>Router</title>
</head>
<body>
<ul>
<li><a href=
"#/home"
rel=
"external nofollow"
>home</a></li>
<li><a href=
"#/about"
rel=
"external nofollow"
>about</a></li>
<div id=
"routeView"
></div>
</ul>
</body>
<script>
function
Router() {
this
.routeView =
null
;
// 组件承载的视图容器
this
.routes = Object.create(
null
);
// 定义的路由
}
// 绑定路由匹配后事件
Router.prototype.route =
function
(path, callback) {
this
.routes[path] = () =>
this
.routeView.innerHTML = callback() ||
""
;
};
// 初始化
Router.prototype.init =
function
(root, rootView) {
this
.routeView = rootView;
// 指定承载视图容器
this
.refresh();
// 初始化触发
// 监听hashchange事件用以刷新
window.addEventListener(
"hashchange"
,
this
.refresh.bind(
this
),
false
);
};
// 刷新视图
Router.prototype.refresh =
function
() {
let hash = location.hash;
console.log(
"refresh"
, hash);
if
(
this
.routes[hash])
this
.routes[hash]();
else
this
.routeView.innerHTML =
""
;
};
window.Router =
new
Router();
Router.route(
"#/home"
,
function
() {
return
"home"
;
});
Router.route(
"#/about"
,
function
() {
return
"about"
;
});
Router.init(document, document.getElementById(
"routeView"
));
</script>
</html>
|
ReactRouter
的实现,commit id
为eef79d5
,TAG
是4.4.0
,在这之前我们需要先了解一下history
库,history
库,是ReactRouter
依赖的一个对window.history
加强版的history
库,其中主要用到的有match
对象表示当前的URL
与path
的匹配的结果,location
对象是history
库基于window.location
的一个衍生。ReactRouter
将路由拆成了几个包: react-router
负责通用的路由逻辑,react-router-dom
负责浏览器的路由管理,react-router-native
负责react-native
的路由管理。BrowserRouter
组件为例,BrowserRouter
在react-router-dom
中,它是一个高阶组件,在内部创建一个全局的history
对象,可以监听整个路由的变化,并将history
作为props
传递给react-router
的Router
组件,Router
组件再会将这个history
的属性作为context
传递给子组件。
1
2
3
4
5
6
7
8
|
// packages\react-router-dom\modules\HashRouter.js line 10
class BrowserRouter extends React.Component {
history = createHistory(
this
.props);
render() {
return
<Router history={
this
.history} children={
this
.props.children} />;
}
}
|
接下来我们到Router组件,Router组件创建了一个React Context环境,其借助context向Route传递context,这也解释了为什么Router要在所有Route的外面。在Router的componentWillMount中,添加了history.listen,其能够监听路由的变化并执行回调事件,在这里即会触发setState。当setState时即每次路由变化时 -> 触发顶层Router的回调事件 -> Router进行setState -> 向下传递 nextContext此时context中含有最新的location -> 下面的Route获取新的nextContext判断是否进行渲染.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
// line packages\react-router\modules\Router.js line 10
class Router extends React.Component {
static computeRootMatch(pathname) {
return
{ path:
"/"
, url:
"/"
, params: {}, isExact: pathname ===
"/"
};
}
constructor(props) {
super
(props);
this
.state = {
location: props.history.location
};
// This is a bit of a hack. We have to start listening for location
// changes here in the constructor in case there are any <Redirect>s
// on the initial render. If there are, they will replace/push when
// they mount and since cDM fires in children before parents, we may
// get a new location before the <Router> is mounted.
this
._isMounted =
false
;
this
._pendingLocation =
null
;
if
(!props.staticContext) {
this
.unlisten = props.history.listen(location => {
if
(
this
._isMounted) {
this
.setState({ location });
}
else
{
this
._pendingLocation = location;
}
});
}
}
componentDidMount() {
this
._isMounted =
true
;
if
(
this
._pendingLocation) {
this
.setState({ location:
this
._pendingLocation });
}
}
componentWillUnmount() {
if
(
this
.unlisten)
this
.unlisten();
}
render() {
return
(
<RouterContext.Provider
children={
this
.props.children ||
null
}
value={{
history:
this
.props.history,
location:
this
.state.location,
match: Router.computeRootMatch(
this
.state.location.pathname),
staticContext:
this
.props.staticContext
}}
/>
);
}
}
|
我们在使用时都是使用Router来嵌套Route,所以此时就到Route组件,Route的作用是匹配路由,并传递给要渲染的组件props,Route接受上层的Router传入的context,Router中的history监听着整个页面的路由变化,当页面发生跳转时,history触发监听事件,Router向下传递nextContext,就会更新Route的props和context来判断当前Route的path是否匹配location,如果匹配则渲染,否则不渲染,是否匹配的依据就是computeMatch这个函数,在下文会有分析,这里只需要知道匹配失败则match为null,如果匹配成功则将match的结果作为props的一部分,在render中传递给传进来的要渲染的组件。Route接受三种类型的render props,<Route component>、<Route render>、<Route children>,此时要注意的是如果传入的component是一个内联函数,由于每次的props.component都是新创建的,所以React在diff的时候会认为进来了一个全新的组件,所以会将旧的组件unmount再re-mount。这时候就要使用render,少了一层包裹的component元素,render展开后的元素类型每次都是一样的,就不会发生re-mount了,另外children也不会发生re-mount.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
// \packages\react-router\modules\Route.js line 17
class Route extends React.Component {
render() {
return
(
<RouterContext.Consumer>
{context => {
invariant(context,
"You should not use <Route> outside a <Router>"
);
const location =
this
.props.location || context.location;
const match =
this
.props.computedMatch
?
this
.props.computedMatch
// <Switch> already computed the match for us
:
this
.props.path
? matchPath(location.pathname,
this
.props)
: context.match;
const props = { ...context, location, match };
let { children, component, render } =
this
.props;
// Preact uses an empty array as children by
// default, so use null if that's the case.
if
(Array.isArray(children) && children.length === 0) {
children =
null
;
}
if
(
typeof
children ===
"function"
) {
children = children(props);
// ...
}
return
(
<RouterContext.Provider value={props}>
{children && !isEmptyChildren(children)
? children
: props.match
? component
? React.createElement(component, props)
: render
? render(props)
:
null
:
null
}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}
|
我们实际上我们可能写的最多的就是Link这个标签了,所以我们再来看一下<Link>组件,我们可以看到Link最终还是创建一个a标签来包裹住要跳转的元素,在这个a标签的handleClick点击事件中会preventDefault禁止默认的跳转,所以实际上这里的href并没有实际的作用,但仍然可以标示出要跳转到的页面的URL并且有更好的html语义。在handleClick中,对没有被preventDefault、鼠标左键点击的、非_blank跳转的、没有按住其他功能键的单击进行preventDefault,然后push进history中,这也是前面讲过的路由的变化与 页面的跳转是不互相关联的,ReactRouter在Link中通过history库的push调用了HTML5 history的pushState,但是这仅仅会让路由变化,其他什么都没有改变。在Router中的listen,它会监听路由的变化,然后通过context更新props和nextContext让下层的Route去重新匹配,完成需要渲染部分的更新.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// packages\react-router-dom\modules\Link.js line 14
class Link extends React.Component {
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);
}
}
render() {
const { innerRef, replace, to, ...rest } =
this
.props;
// eslint-disable-line no-unused-vars
return
(
<RouterContext.Consumer>
{context => {
invariant(context,
"You should not use <Link> outside a <Router>"
);
const location =
typeof
to ===
"string"
? createLocation(to,
null
,
null
, context.location)
: to;
const href = location ? context.history.createHref(location) :
""
;
return
(
<a
{...rest}
onClick={event =>
this
.handleClick(event, context.history)}
href={href}
ref={innerRef}
/>
);
}}
</RouterContext.Consumer>
);
}
}
|
每日一题 。
https://github.com/WindrunnerMax/EveryDay 。
参考 。
https://zhuanlan.zhihu.com/p/44548552 https://github.com/fi3ework/blog/issues/21 https://juejin.cn/post/6844903661672333326 https://juejin.cn/post/6844904094772002823 https://juejin.cn/post/6844903878568181768 https://segmentfault.com/a/1190000014294604 https://github.com/youngwind/blog/issues/109 http://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html 。
到此这篇关于ReactRouter的实现方法的文章就介绍到这了,更多相关ReactRouter的实现内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.cnblogs.com/WindrunnerMax/p/14322330.html 。
最后此篇关于ReactRouter的实现方法的文章就讲到这里了,如果你想了解更多关于ReactRouter的实现方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我们正在现有的多页面应用程序中添加一些 react-router,打算慢慢过渡到 SPA 设置。使用 Link 链接到尚未(尚未)由 Router 处理的页面时会出现问题。 在我的主开关中,我有一个
我们是 袋鼠云数栈 UED 团队 ,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:景明 升级背景
我正在构建 React + redux + reactRouter 应用程序。我有一个 api,它在登录后向我发送 token ,因此我可以保护我的 api,但我有前端路由,应该受到保护(如仪表板)。
我有一个奇怪的问题,似乎与 webpack 有关,但我不确定。这是上下文,我正在使用 ReactJS 创建一个前端,并且我有一个 Java 后端,所以我想做的是编译和捆绑所有 JS使用webpack和
我正在尝试通过 express 从我的服务器提供 React。 不过,当我点击 localhost:3000 时,我得到的错误是: TypeError: (0 , _reactRouter.match
我想为 React 路由器创建一个过渡动画,但它似乎不起作用(没有过渡)。 正如你在下面看到的,我所做的就是创建一个组件 FadeRoute,它使用 ReactCSSTransitionGroup 创
我是 React 的新手。我通过直接将 react.js 和 react-dom.js 文件以及用于 javascript 转换的 browser.min.js 添加到我的 HTML 页面中进行练习来
我正在使用 render={() => } 通过 React Router 4 渲染组件 我需要将状态传递给给定的组件,即: export const Routes: React.SFC = () =
我正在编写一个歌词抓取应用程序(ReactJS+Redux+React路由器),它由一个登录页和一个搜索表单组成,该应用程序在该页面上呈现用户所在国家的前10首曲目,以及一个搜索表单来搜索特定的曲目。
我正在编写一个歌词抓取应用程序(ReactJS+Redux+React路由器),它由一个登录页和一个搜索表单组成,该应用程序在该页面上呈现用户所在国家的前10首曲目,以及一个搜索表单来搜索特定的曲目。
我正在编写一个歌词抓取应用程序(ReactJS+Redux+React路由器),它由一个登录页和一个搜索表单组成,该应用程序在该页面上呈现用户所在国家的前10首曲目,以及一个搜索表单来搜索特定的曲目。
我正在使用 React 和 Redux 构建一个多语言应用程序: { "react": "16.2.0", "redux": "3.7.2", "react-redux": "
我正在尝试在我的 React 单页应用程序中实现 Google Analytics,但要做到这一点,我知道我需要使用 history支柱。我正在使用 Switch 设置,但这似乎不起作用。
我是一名优秀的程序员,十分优秀!