React-router进阶

一、基于路由阐述web前端发展的三个阶段

第一阶段:后端路由阶段

路由的由来
路由一词其实最早是网络工程中的专业术语,代表通过互联网把信息从源地址传输到目的地址的一个活动,后来这个概念被web开发的人员所借鉴,所以才有了现在的前端开发中的路由。

什么是后端渲染
后端渲染也叫作服务器端渲染(Servier Side Render)。早期的web开发中,整个HTML页面都是由后端开发人员用java语言写的比如JSP的页面,直接在后端将HTML页面渲染完成,然后发送给客户端浏览器,浏览器解析HTML代码即可呈现一个页面,这种将HTML页面直接在后端渲染完成后拿到前端展示的渲染模式叫做后端渲染。

什么是后端路由
每一个页面都有一个对应的url网址,浏览器会将URL发送到服务器,服务器会基于正则表达式对该URL进行匹配,最后交给一个Controll进行处理,Controll处理完成之后返回一个HTML页面,返回给客户端浏览器。针对于每一个前端的URL后端都有一个对应的HTML页面返回,这种由后端来管理和维护的 URL地址-HTML页面 的路由映射关系,叫做后端路由。

模式优点
后端渲染好的html页面浏览器可以直接进行解析渲染呈现出来,不再需要去加载css和js等静态资源,有利于SEO的优化并且没有js和css加载的阻塞,也有利于减少首屏(FP)呈现的时间。

模式缺点
缺点:负责结构的HTML页面和逻辑会混合在一起,不符合W3C提倡的结构行为样式相分离;而且也不利于项目的维护,因为一个前端还得会后端语言才可以进行维护.

第二阶段:前后端分离阶段

什么是前端渲染

客户端浏览器在输入url请求页面的过程中,该页面所对应的HTML+css+js+JQuery等静态资源从静态资源服务器进行获取,浏览器拿到这些资源先进行渲染,此时前端页面的基本框架已经呈现,但是一些动态展示的列表等数据,需要基于前面请求的js代码中的ajax请求,从对应的专门提供API接口(restful规范)的API服务器获取到数据,然后前端开发人员再通过js代码将这部分数据插入到当前的HTML页面中,这种前端负责渲染HTML页面,后端只负责设计API接口和提供API文档的阶段就是前后端分离阶段,并且这种交由js来渲染页面的技术叫做前端渲染。

模式优点
将前后端进行了一个清晰的分离,前端负责页面和交互;后端负责api接口和数据处理
提供API接口的服务器不仅仅可以给web端页面提供数据,给移动端等多端都可以提供数据
相比于后端路由阶段,这个阶段的路由还是后端管理,但是将之前的页面渲染这个活交给了前端,后端只负责数据处理。

第三阶段:单页面富应用阶段

单页面富应用中的前端渲染

单页面富应用的意思是single-page-application,简称SPA。意思就是整个web应用只有一个HTML页面,当我们在页面上触发某个操作改变页面地址栏中的URL的时候,通过javascript来监听浏览器地址栏中URL的改变,并根据改变后的URL渲染对应的页面(组件),很明显这种通过js来渲染页面或组件的方式也属于前端渲染。

单页面富应用中的前端路由

这种由前端的javascript代码来管理和维护的URL地址和页面(组件)一一对应的映射关系,就是前端路由。前端路由可以根据url的不同,最终让框架(Vue/React)来渲染不同的组件,最终我们在页面上看到的实际就是渲染的一个个组件页面。

二、前端路由的底层原理

1. 前端路由的两个特点

要实现一个前端路由,必须要满足以下两个条件:
第一:当浏览器地址栏中的URL发生变化的时候,不能引起页面的刷新,也就是不会向后端服务器发送请求。目前前端开发来说,实现这一个要求有两种方案,一种是URL的hash模式;另外一种是HTML5新增的history API。

第二:需要在前端监听URL地址栏中的变化,并且前端可以通过判断当前的URL来决定页面要渲染哪些元素(组件)

只要能实现以上两个条件,那么就是一个前端路由的简单实现,其实诸如Vue\React等框架的router的底层原理也是基于以上来实现的。

2. 基于URL的hash实现一个前端路由

原理:浏览器的URL的hash其实就是锚点(#),它的改变不会引起浏览器页面的刷新。首先实现前端路由的第一个条件满足;同时可以基于window.onhashchange来监听URL中hash值的变化,并在事件回调处理函数中根据location.hash获取到当前地址栏的hash值,然后判断渲染那个组件或者页面。

注意:改变URL的hash值的方法有两个,一个是直接在a标签的href中进行赋值;一种是通过直接给window.location.hash赋值来修改url中的hash值,不管是那种方法,本质都是改变的window.location的href属性。

优点:兼容性较好,低版本浏览器都可以使用

缺点:地址栏中有一个#号,不美观

简单实现一个基于修改URL的hash来完成前端路由的demo

<div id="app">
	<a href="#/home">首页</a>
	<a href="#/about">关于</a>
</div>
<div class="router-view"></div>

<script type="text/javascript">
	let routerView = document.querySelector('.router-view');
	let aBtns = document.querySelectorAll('a');
	/* 监听地址栏中URL的hash值发生改变 */
	window.onhashchange = function(e){
		console.log(window.location.hash);
		switch(window.location.hash){
			case '#/home':
				routerView.innerHTML = "首页页面的内容"
				break;
			case '#/about':
				routerView.innerHTML = "关于页面的内容"
				break;
			default:
				routerView.innerHTML = ""
				break;
		}
	}
</script>

3. 基于HTML5的history API来实现

HTML5的5个history API

  • replaceState(state,title,newUrl) 新路径替换原来的路径,不新建新的历史记录
  • pushStata(state,title,newUrl) 跳转新的路径,新建新的历史记录
  • go(number) 向前或者向后改变路径
  • forward(number) 前进路径
  • back(number) 后退路径

pushStata方法的参数

pushStata(state,title,newUrl)

  • state
    状态对象 — 状态对象state是一个JavaScript对象, 状态对象可以是能被序列化的任何东西,一般为空对象{}
  • title
    跳转过去的页面标题,一般为空字符串""
  • newUrl
    新的历史URL记录,字符串

'home'和'/home'在页面跳转时路径的变化

比如当前url的基础路径为:http://localhost:3000/alibaba/home

如果这里传递的newUrl为'list',那么是基于当前所在目录的路径进行跳转的,跳转之后的新路径为:

默认浏览器地址栏:"http://localhost:3000/alibaba/home"
history.pushState({},"",'list')
新的浏览器地址栏:"http://localhost:3000/alibaba/list"
也就是只传递一个字符串,等于访问的是当前目录alibaba下的list文件

如果这里传递的newUrl为'/list',那么是基于根路径进行跳转的,跳转之后的新路径为:

默认浏览器地址栏:"http://localhost:3000/alibaba/home"
history.pushState({},"",'/list')
新的浏览器地址栏:"http://localhost:3000/list"
也就是传递一个前面加/的字符串,等于访问的是根目录下的list文件

window.onpopstate事件

注意: pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。

注意:调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法),此外,a 标签的锚点也会触发该事件.

6个可以触发该事件执行的操作

  1. 调用history.go()
  2. 调用history.forward()
  3. 调用history.back()
  4. 点击浏览器的前进按钮
  5. 点击浏览器的后退按钮
  6. a标签的锚点点击也会触发,但是会刷新页面,所以需要阻止默认事件

简单实现一个基于HTML5的history API来实现前端路由的demo

条件一:url变化但是不刷新页面
当点击a标签的时候url会发生变化,但是阻止a标签的默认行为,通过监听a标签的点击事件,通过HTML5新增的5history的pushState或者replaceState方法实现URL路径的变化但不刷新页面。除此之外,history的那5个api都具有改变url且不刷新页面的情况。

基于getAttribute方法获取到当前被点击的a标签的href属性,来手动执行一遍自定义的urlChange方法,其实也就是一个路由映射表。

条件二:url变化可以监听并且基于不同的url渲染不同的页面
基于window.onpopstate来进行监听页面的前进以及后退操作,监听到之后基于location.pathname获取到地址栏中路径,然后手动执行一遍自定义的urlChange方法。

优点:没有#号,比较符合常规的URL地址的写法

缺点:
HTML5新特性,兼容性不够好;
并且如果后端没有进行配置的话,点击浏览器的刷新按钮会导致404 not found

<body>
	<div id="app">
		<a href="/home">首页</a>
		<a href="/about">关于</a>
	</div>
	<div class="router-view"></div>
	
	<script type="text/javascript">
		let routerView = document.querySelector('.router-view');
		let aBtns = document.querySelectorAll('a');
		/* 监听a标签的点击 阻止默认事件 */
		for(let i=0;i<aBtns.length;i++){
			aBtns[i].onclick = (e)=>{
				console.log('a标签发生点击');
				e.preventDefault();
				const href = aBtns[i].getAttribute('href');
				history.pushState({},"",href)
				/* 手动调用一次urlChange */
				urlChange();
			}
		}
		
		/* window.onpopstate监听浏览器的前进后退按钮点击 以及history.go()API的调用情况 */
		window.addEventListener('popstate',urlChange);
		
		/* 自定义urlChange函数 */
		function urlChange(){
			console.log('路径发生了改变');
			switch(window.location.pathname){
				case '/home':
					routerView.innerHTML = "首页页面的内容"
					break;
				case '/about':
					routerView.innerHTML = "关于页面的内容"
					break;
				default:
					routerView.innerHTML = ""
					break;
			}
		}
	</script>
</body>

三、React-Router的基本使用

1.react-router使用前的安装和依赖

从react-router@4.0开始,路由不再集中在一个包里面进行管理了,由以下几个包分包管理:
react-router是router的核心部分代码
react-router-dom是用于浏览器的
react-router-native是用于原生native应用的

我们在使用的时候只需要安装一个react-router-dom包就可以了,因为react-router-dom这个库依赖react-router,所以会自动将react-router等它依赖的库都进行安装。

2.react-router的一些基本API组件

react-router主要提供了以下组件类型的API,通过这些API可以构建一个前端路由。

  1. 路由模式API
    react-router中包含了对路径改变的监听,并且会将相应的路径传递给子组件
    BrowserRouter:代表底层采用HTML5的history API来实现前端路由
    HashRouter:代表底层采用hash模式来实现前端路由

  2. 导航元素API
    都是基于to属性指向的路径来实现路径的跳转
    Link:用来实现路由跳转的点击按钮,最终会被渲染为一个a标签
    NavLink:在Link的基础上做了一些属性的增强

  3. 展示渲染API
    Route组件主要用于路径的匹配,并根据匹配到的结果在该组件内部渲染出组件内容,等于是一个组件渲染的盒子。
    path属性:设置匹配到的路径
    component属性:用于设置路径匹配到之后用于渲染的组件
    exact属性:精确匹配路径,只有路径完全一致才会渲染对应组件,模糊匹配不渲染

import React,{PureComponent} from 'react';

import Home from './react-router/home.js'
import About from './react-router/about.js'
import Profile from './react-router/profile.js'

import {
	BrowserRouter,
	HashRouter,
	Link,
	NavLink,
	Route
}from "react-router-dom"

class App extends PureComponent{
	render(){
		return(
			<div>
				<BrowserRouter>
					<Link to="/">首页</Link>
					<Link to="/about">关于</Link>
					<Link to="/profile">我的</Link>
					
					<Route path="/" component={Home} exact></Route>
					<Route path="/about" component={About}></Route>
					<Route path="/profile" component={Profile}></Route>
				</BrowserRouter>
			</div>
		)
	}
}
export default App;

3.NavLink组件

  • to属性:和Link组件一样,都指向当前用户点击之后要跳转的路由
  • exact属性:精确匹配

NavLink的exact代表浏览器的url path在匹配该组件的to属性值的时候,要采用精确匹配,以便于给这些组件渲染出来的a元素动态添加active样式类名

  • component属性:当路由匹配成功之后要渲染的组件

  • activeClassName属性:当前路由处于激活状态下的时候,为了防止类名冲突,自定义添加的class类名

注意:一般情况下,只要当前路径匹配成功之后,就会自动给当前的Link或者NavLink渲染出来的a标签添加上一个名为active的class类名,我们可以在全局公共的App.css样式文件中定义路由激活时的样式,如果担心active类名和其他地方用到的冲突,那么需要使用NavLink组件提供的activeClassName来自定义一个class类名解决。

<NavLink exact to="/" activeClassName="home-active">首页</NavLink>
<NavLink to="/about" activeClassName="about-active">关于</NavLink>
<NavLink to="/profile" activeClassName="profile-active">我的</NavLink>
  • activeStyle属性:当前路由处于激活状态下的css样式,直接以行内样式定义,值为一个对象
<NavLink exact to="/" activeStyle={{color:"pink",fontSize:"18px"}}>首页</NavLink>
<NavLink to="/about" activeStyle={{color:"pink",fontSize:"18px"}}>关于</NavLink>
<NavLink to="/profile" activeStyle={{color:"pink",fontSize:"18px"}}>我的</NavLink>

> 所对应的App.css文件中书写路由激活时按钮的样式
a.active{
	font-size: 20px;
	color: brown;
	margin: 0 10px;
}

/* 自定义的类名 */
a.home-active{
	font-size: 20px;
	color: blue;
	margin: 0 10px;
}

4.react-router注意事项

  • 每一个定义在组件中的Link或者NavLink组件都会渲染在页面上
  • 不是每一个Route组件都会进行渲染,可以把Route组件看作一个占位符,或者看作一个盒子。只有当Route组件的path属性和浏览器地址栏中的pathname对应起来,这时候这个盒子内才会展示对应的component组件。
  • Route组件可以自定义位置以决定一旦路径匹配成功之后所对应的组件要展示在那个位置

5. Switch组件

除了Route组件定义一个path值固定的路由之外,日常开发中还会有两种路由:

  • 动态路由:比如 代表只有home后面跟任意值都可以进行匹配
  • NoMatch:当所有Route的path都不匹配的时候,才展示的组件

实际开发中,我们希望的是只要有一个路由Route匹配成功,就中止后续的匹配,没必要将后续有可能匹配到的动态路由或者NoMatch组件再渲染出来,类似一种排他的思想。要实现这个效果,就需要用到Switch组件,将所有Route组件包裹在内,只要有一个匹配成功就中止后续匹配。

<BrowserRouter>
	<NavLink exact to="/">首页</NavLink>
	<NavLink to="/about" >关于</NavLink>
	<NavLink to="/profile">我的</NavLink>
	<NavLink to="/user">用户信息</NavLink>
	
	<Switch>
		<Route exact path="/" component={Home} ></Route>
		<Route path="/about" component={About}></Route>
		<Route path="/profile" component={Profile}></Route>
		<Route path="/:id" component={User}></Route>
		<Route component={NoMatch}></Route>
	</Switch>
</BrowserRouter>

6. Redirect组件

Redirect组件用于路由的重定向,一旦再某个组件渲染的时候渲染到了Redirect组件那么会直接重定向到该组件的to属性指向的地址,然后基于地址来渲染对应的组件。

利用这一特性可以做一个建议的判断用户进入某个页面是否已经登录:
如果登录,那么展示该组件的页面;
如果没有登录,那么直接重定向到登录页面

link和navlink必须和用户交互才可以,redirect只要有就会直接跳转
一旦出现redirect组件就马上进行路由的重定向,立马去Route中找和自己to属性匹配的path,渲染对应的组件
自动跳转的时候使用

class User extends PureComponent{
	constructor(props) {
	    super(props);
		this.state = {
			isLogin:false
		}
	}
	render(){
		return this.state.isLogin?(
			<div>
				<h2>这是User组件的内容,这里展示用户信息</h2>
				<h3>用户名:直接起飞⑧</h3>
				<h3>密码:66668888</h3>
			</div>
		):<Redirect to="/login" />
	}
}

7. 路由的嵌套

子路由是写在父组件指向的那个组件中的,是在父路由对应的组件的容器中渲染的
子路由的NavLink也可以使用activeClassName来自定义路由激活时按钮的类名

import React,{PureComponent} from 'react';
import {
	BrowserRouter,
	HashRouter,
	Link,
	NavLink,
	Route,
	Switch
}from "react-router-dom"

function AboutUs(props){
	return <div>关于我们那可说的多了</div>
}

function AboutHistory(props){
	return <div>公司的历史就不用我多说了吧</div>
}

function AboutCompany(props){
	return <div>我们公司就是一个字:钱少事多</div>
}

class About extends PureComponent{
	render(){
		return(
			<div>
				<h3>这是About组件的内容</h3>
				<h5>
					<NavLink exact to="/about" activeClassName="about-active">关于我们</NavLink>
					<NavLink to="/about/history" activeClassName="about-active">公司历史</NavLink>
					<NavLink to="/about/company" activeClassName="about-active">公司介绍</NavLink>
					
					<Route exact path="/about" component={AboutUs}></Route>
					<Route path="/about/history" component={AboutHistory}></Route>
					<Route path="/about/company" component={AboutCompany}></Route>
				</h5>
			</div>
		)
	}
}
export default About;

四、使用history Api来实现手动路由跳转

react-router为我们实现路由跳转提供了两种途径:

  1. 基于组件Link或者NavLink的to属性来实现路由的跳转
  2. 基于组件的history api来主动调用实现路由的跳转
    而要手动调用api来实现路由跳转,最主要的是先获取到history对象,然后进行push或者replace操作

1. 基于Route组件创建并渲染出来的组件可以直接获取history等属性

如果一个组件本身就是基于Route直接跳转过来的,那么react-router会对这个组件做一个属性增强,在这个组件的props属性中可以直接获取history、loctaion以及match对象。

history对象
go goBack goForward push replace五个historyApi的封装

{
	action: "PUSH"
	block: ƒ block(prompt)
	createHref: ƒ createHref(location)
	go: ƒ go(n)
	goBack: ƒ goBack()
	goForward: ƒ goForward()
	length: 36
	listen: ƒ listen(listener)
	location: {pathname: "/goods", search: "", hash: "", state: undefined, key: "ewkjlo"}
	push: ƒ push(path, state)
	replace: ƒ replace(path, state)
}

location对象

{
	hash: ""
	key: "sorfsk"
	pathname: "/profile"
	search: ""
	state: null
}

match对象

{
	isExact: true
	params: {}
	path: "/profile"
	url: "/profile"
}

2. 基于JSX语法渲染的组件必须通过withRouter高阶组件进行属性增强后才可以获取history等属性

如果是一个普通的经过JSX语法渲染的组件,那么该组件的props属性是一定没有history这些对象的,这时候我们就需要想办法给该组件进行一个属性增强。
react-router已经为我们提供了进行属性增强的办法,那就是使用withRouter高阶组件。一般需要两步来完成:

第一步:假设Home组件想进行增强,那么需要导出一个withRouter高阶组件增强后的Home组件

import {withRouter}from "react-router-dom"
class Home extends PureComponent{
	navToIndex(){
		console.log(this.props.history)
		this.props.history.push('/index');
	}
	render(){
		return(
			<div>
				<P>Home组件</P>
				<button onClick={e=>this.navToIndex()}>跳转至首页</button>
			</div>
			
		)
	}
}
export default withRouter(Home);

第二步:Home组件在App组件中使用的时候,必须使用BroswerRouter或者HashRouter组件进行包裹才会生效

import {BrowserRouter}from "react-router-dom"
class App extends PureComponent{
	render(){
		return(
			<div>
				<Broswer>
					<Home></Home>
				</Broswer>
			</div>
		)
	}
}
export default App;

六、React路由跳转时的参数传递

一般来说,在React中当路由跳转的时候传递参数有三种方式:

  1. 简单参数直接通过动态路由传递即可,基于match.params取值
  2. 复杂对象参数可通过query查询参数传递,基于location.search取值
  3. 复杂对象参数可通过Link或者NavLink中to属性的值是对象的特点,将参数通过state进行传递

1.动态路由 /path/:param[缺点是只能传递简单的参数]

一般动态路由指的是路由中的路径不会固定,而是根据某个参数在一直变化,并且基于该路由渲染出来的组件也是基于这个参数的变化,该组件内部的内容也跟着变化,它指的是路径是变化的。

其次比如/detail的path对应一个Detail组件,该组件专门展示商品的详情。但是如果将匹配组件Route的path写成/detail/id,那么不管这个id传递什么值,加载的都是Detail组件,这种匹配的规则也可以称为动态路由。

在react-router搭建的路由中,通过组件的props的match属性中的params属性获取动态路由跳转时携带的参数.

NavLink组件定义动态路由

const goodsId = 10086;
<NavLink to={`/detail/${goodsId}`}></NavLink>

Route组件匹配动态路由规则

<Route path="/detail/:id" component={Detail}></Route>

Detail组件中获取动态路由this.props.match.params
match对象中的path属性和url属性在绝大部分情况下都是相同的,只有在动态路由的情况下才不一样
path: "/detail/:id" 获取的是在配置路由表的时候的动态路由配置
url: "/detail/10086" 获取的是实际跳转之后浏览器地址栏中的path

import React,{PureComponent} from 'react';
class Detail extends PureComponent{
	render(){
		console.log(this.props.match);
		
		let id = this.props.match.params.id;
		return(
			<div>
				这是ID为{`${id}`}的商品详情
			</div>
		)
	}
}
export default Detail;

2.基于类似于query查询参数传递参数,通过location.search拿到参数

缺点是传递的时候参数比较多的对象会在地址栏中需要拼接很长的字符串
然后拿的时候拿到了还需要自己写方法然后解析,所以现在react已经不推荐使用这种方法传递参数了

传递的时候类似query查询参数传递

<NavLink to={`/detail1?name=lilei&age=18`}>商品详情1</NavLink>

<Route path="/detail1" component={Detail1}></Route>

跳转过去以后基于location.search拿到参数然后自己写方法解析

import React,{PureComponent} from 'react';

class Detail extends PureComponent{
	/* 封装解析查询参数为对象的方法 */
	parseInfo(str){
		let arr = str.slice(1).split('&');
		let obj = {};
		arr.forEach(item=>{
			let tempArr = item.split('=');
			obj[tempArr[0]] = tempArr[1];
		})
		return obj;
	}
	render(){
		/* 获取路由传递的查询参数 */
		let userInfo = this.props.location.search;
		/* 获取解析后的对象 */
		const parseInfoObj = this.parseInfo(userInfo);
		return(
			<div>
				用户姓名为:{parseInfoObj.name}
				用户年龄为:{parseInfoObj.age}
			</div>
		)
	}
}

export default Detail;

3.基于Link或者NavLink的to属性传递,然后通过location

Link或者NavLink组件都有一个to属性可以用来指向当点击按钮之后要跳转的路由,这个to属性除了可以接受一个字符串类型的路径比如'/detail'之外,还可以接受一个对象作为其属性值,这个对象有如下属性参数:

  • pathname属性:值为字符串,指向要跳转的路由路径
  • search属性:值为字符串,类似于query查询参数
  • hash属性:值为字符串,代表hash值
  • state属性,值为对象,代表可以保存到location对象的一个状态,我们可以将比较复杂的对象参数直接传递给state属性

传递的时候基于to属性的state参数接收复杂对象参数

<NavLink to={{
	pathname:'/detail2',
	search:'?job=coder&car=benz',
	hash:'',
	state:{
		name:'lilei',
		age:18,
		study:'good'
	}}}>
	商品详情2
</NavLink>

<Route path="/detail2" component={Detail2}></Route>

接收的时候基于location.state拿到参数

import React,{PureComponent} from 'react';

class Detail extends PureComponent{
	
	render(){
		console.log(this.props.location.state);
		const {name,age,study} = this.props.location.state;
		return(
			<div>
				这是用户姓名{name};
				这是用户年龄{age};
				这是用户学习{study};
				
			</div>
		)
	}
}
export default Detail;

七、集中配置路由组件映射表

之前基于react-route提供的Switch组件和Route组件,我们保证了路由和组件的一一映射关系。但是这种配置路由有一个缺点,那就是配置比较混乱。既不利于维护也不利于二次开发,所以我们需要一个类似vue的那种集中管理项目路由配置表的方式来管理这些路由和组件的映射关系,再react中一般采用第三方库react-router-config来实现。

npm i react-router-config --save

实现原理:将定义再routes数组中的一个个对象,按照Switch的渲染模式,依次渲染为一个个的Route组件。路由对应的组件渲染不再通过Route组件来进行占位,而是直接基于一个函数renderRoutes(routes),传入要渲染的路由表即可实现和原生react渲染路由对应组件一模一样的效果。

集中配置路由映射表

本质就是将原来写在Route组件中的属性换个集中的地方写,一般写在router文件夹下的index.js中
二级子路由是通过一级路由的routes属性来进行配置的
然后调用{renderRoutes(routes)}方法实现路由对应组件的渲染

import Home from '../react-router/home.js';
import About,{AboutUs,AboutHistory,AboutCompany}from '../react-router/about.js';
import Profile from '../react-router/profile.js';
import Detail1 from '../react-router/detail1.js';


const routes = [
	{
		path:'/',
		component:Home,
		exact:true
	},
	{
		path:'/home',
		component:Home,
	},
	{
		path:'/about',
		component:About,
		routes:[
			{
				path:'/about',
				component:AboutUs,
				exact:true
			},
			{
				path:'/about/history',
				component:AboutHistory,
			},
			{
				path:'/about/company',
				component:AboutCompany,
			}
		]
	},
	{
		path:'/profile',
		component:Profile,
	},
	{
		path:'/detail1',
		component:Detail1,
	},
]
export default routes;

renderRoutes(routes)方法源码

该方法的参数主要是routes,也就是用于渲染的路由配置数组,里面是一个个的路由配置对象。
该方法是 react-router-config这个库导出的,它的主要作用是渲染路由对应的组件,起到和Route组件一样的占位
在渲染子路由的时候,只有父路由是通过renderRoutes方法渲染出来的,那么这个父路由对应的组件身上才会有route属性,这个props.routes属性的值就是这个父路由配置在表里面的子路由routes数组。
如果父路由是基于Switch组件渲染出来的,那么这个父路由对应的组件是没有这个获取子路由routes数组的属性的。

function renderRoutes(routes, extraProps, switchProps) {
  if (extraProps === void 0) {
    extraProps = {};
  }

  if (switchProps === void 0) {
    switchProps = {};
  }
  /* 如果传递了routes,那么返回一个Switch组件,它的子组件是routes数组中的每一个对象,然后子组件是一个个的Route组件,这也就说明renderRoutes函数的本质还是把路由配置表转化为Switch包裹的Route组件 */
  return routes ? React.createElement(reactRouter.Switch, switchProps, routes.map(function (route, i) {
    return React.createElement(reactRouter.Route, {
      key: route.key || i,
      path: route.path,
      exact: route.exact,
      strict: route.strict,
      render: function render(props) {
        return route.render ? route.render(_extends({}, props, {}, extraProps, {
          route: route
        })) : React.createElement(route.component, _extends({}, props, extraProps, {
          route: route
        }));
      }
    });
  })) : null;
}

matchRoutes()方法源码

这个方法是一个类工具函数,接收一个需要查询的路由配置数组和一个路由的path,查询当前路由配置数组中的哪些配置是符合这个path的。

function matchRoutes(routes, pathname,branch) {
  if (branch === void 0) {
    branch = [];
  }

  routes.some(function (route) {
    var match = route.path ? reactRouter.matchPath(pathname, route) : branch.length ? branch[branch.length - 1].match // use parent match
    : reactRouter.Router.computeRootMatch(pathname); // use default "root" match

    if (match) {
      branch.push({
        route: route,
        match: match
      });

      if (route.routes) {
        matchRoutes(route.routes, pathname, branch);
      }
    }

    return match;
  });
  return branch;
}

合并对象的方法_extends方法源码

可以学习手写Object.assign(target,...sources)

function _extends() {
	  _extends = Object.assign || function (target) {
		for (var i = 1; i < arguments.length; i++) {
		  var source = arguments[i];
	
		  for (var key in source) {
			if (Object.prototype.hasOwnProperty.call(source, key)) {
			  target[key] = source[key];
			}
		  }
		}
	
		return target;
	  };
	
	  return _extends.apply(this, arguments);
	}

手写的Object.assign(target,...sources)方法

(function(){
	function myAssign(target,...sources){
		for(let i=0;i<sources.length;i++){
			let source = sources[i];
			for(let key in source){
				if(source.hasOwnProperty(key)){
					target[key] = source[key];
				}
			}
		}
		return target;
	}
})()

原文地址:https://www.cnblogs.com/gaokai/p/15113452.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom