Route组件可以说是React-router的核心,Route的功能有两大块,一是路径匹配,二是渲染组件。
Route路径匹配 - Path属性
Path属性可以传入一个字符串或正则表达式(React-Router库使用path-to-regexp@^1.7.0这个库来解析),或者字符串(正则表达式)的数组。和后端很类似,这个匹配可以是动态的,也可以动态的获取匹配成功的动态路径。
exact
和Path搭配使用的属性还有exact,这个只要加上就表示true,不加上就是false。加上exact表示完全相同才会匹配成功,不加上的话,只要开头URL匹配成功的就算是匹配成功。如果多个路径匹配成功,会把匹配的组件全部都渲染出来。
如果不想使用exact标记,可以使用Switch组件套在Route组件外边,然后将匹配根路径的Route放到最后,一样可以达到目的,此时的App.js如下:
function App() {
return (
<BrowserRouter>
<Nav/>
<Switch>
<Route path="/about" component={About}/>
<Route path="/contact" component={Contact}/>
<Route path="/user/:user" component={User}/>
<Route path="/" component={Home}/>
</Switch>
</BrowserRouter>
);
}
Path动态路径写法
之前的例子里是字符串形式的路径,纯粹是静态匹配。实际上很常用的是动态匹配,需要动态的匹配的话,要在Path属性中采取特别的写法,并在组件中使用match
对象加上URL路径变量名来获取实际匹配成功的URL字符串。
来写一个组件看一下:
<Route path="/user/:user" exact component={User}/>
export const User = (props) => (
<p>User name: </p>
);
path属性中的:user
部分,就是动态匹配URL的变量名称,匹配成功的部分会被装载到user变量中,但是如何获取呢?
此时如果访问/user
,/user/
,都不会匹配成功,只有URL为/user/*
的时候才会显示出User组件。不过此时我们还没有获取动态的路径。
match
对象
在每个Route匹配成功的时候,在component属性要渲染的组件中,会被React-router传递一个match对象到props中。match对象非常重要,基于URL的逻辑基本上都和match有关,来看看match的属性。
match.params
通过match.params对象的属性就可以访问到动态路径的内容,就是URL中我们定义的动态路径的变量名,在这个例子中,就是user。
修改一下User
组件来显示动态内容:
export const User = (props) => (
<p>User name: {props.match.params.user}</p>
);
就成功显示出了动态路径中的用户名。
match
的其他属性
match
还有三个属性如下:
- isExact,如果Path上标记了excat,就为true。如果Switch中的内容匹配成功,该属性也为true。
- path,原始的匹配路径,即带有变量的URL,就是Route组件中的path属性内容
- url,实际匹配成功的完整URL(不含域名和端口)
在这个例子中,可以打印出来看一下:
export const User = (props) => (
<>
<p>User name: {props.match.params.user}</p>
<p>Path : {props.match.path}</p>
<p>url : {props.match.url}</p>
<p>isExact : {props.match.isExact? "精确匹配":"非精确匹配"}</p>
</>
);
Route组件渲染
路径匹配成功之后的Route组件,就要渲染对应的组件。
在最开始的基础例子里,使用了Route的component属性,传递了一个组件让其进行渲染。这只是方式之一。来系统的看一下渲染组件相关的内容,主要有三种方式:
使用component属性
component属性在之前已经使用了,传递了一个组件进去。
由于组件就是一个函数,所以传递函数也同样可以执行,只要函数返回一个组件即可:
<Route path="/about" component={(props)=>(<About {...props}/>)}/>
传递函数的缺点在于:每次渲染的时候,传递的函数每次都会重新挂载和执行,因为传递的函数每次都要新建这个函数对象。所以传递函数不如传递已经定义好的组件效率高。
使用render属性
render属性只接受函数类型的值,render函数可以避免在component属性中传递函数的反复挂载问题。
写法其实是一样的:
<Route path="/about" render={(props)=>(<About {...props}/>)}/>
这个组件的状态会在不同的render函数中保存,不会出现component属性的问题。
要注意的是,同时使用component和render属性的时候,component优先级高于render,会导致render失效。
传递函数的方法有的时候比直接传组件更好用,因为可以传递额外的参数,或者创建更加复杂的业务逻辑,比如传个自定义属性:
<Route path="/about" render={(props)=>(<About {...props} myattr={"saner"}/>)}/>
使用children属性
children属性也接受一个返回组件的函数,但是和前两个方法相比,这个属性的不同之处在于:
即使所在的Route组件没有匹配成功,children属性中的逻辑也会被执行。
三种渲染方式,都会把三个属性传递给函数中的props,分别是match,location,history:
- location可以从其中取出app当前的位置
- history是一个可变对象,实际是React-router依赖的history包包装后的原始history对象
- children对象的独特之处在于传进去的props.match在匹配失败的时候是null。另外两种渲染都是成功后才渲染,所以props.match一定不是null。
比如我们来控制,让Contact组件不管什么时候都显示一些东西:
<Route path="/contact" children={(props) => <div>{props.match? "是contact路径":"不是contact路径"}</div>}/>
这种情况下,不管如何,都会显示出当前是不是contact路径。注意,Switch组件内无法触发这个效果。
Route组件的核心内容就是路径匹配和渲染。通过路径匹配可以获取实际的匹配路径,从而动态的执行逻辑,比如从state中获取和计算结果并展示。渲染则提供了三种方式用于各种不同逻辑的跳转。
现在根据不同的URL渲染不同的组件这一个核心要素已经知道了,接下来看看链接与跳转,有了链接和跳转搭配之后,就更加方便了。