上一篇 React-Router 中文简明教程(上) 中讲了 如何创建一个简单的 react 路由,Link 组件的使用,嵌套路由,接着之前的内容,我们来继续 React-Router 之旅~
文章大纲:
本篇示例源码:
react-router-demo-part2
五. 为 Link 组件设置触发状态
有时我们需要在导航链接上设置一个触发状态的样式,让用户知道当前在哪个路由下,比如某宝的底部导航:
一种方法 是使用Link组件的activeStyle属性直接设置行内样式,修改modules/App.js中:
<li><Link to="/about" activeStyle={{ color: 'red' }}>About</Link></li>
<li><Link to="/repos" activeStyle={{ color: 'red' }}>Repos</Link></li>
注意:React 组件的样式属性值必须写在双括号内,这是因为样式在 JSX 语法里被作为一个对象。
另一种更好的方法 是使用Link组件的activeClassName属性,为被触发的导航链接添加一个 Class:
<li><Link to="/about" activeClassName="active">About</Link></li>
<li><Link to="/repos" activeClassName="active">Repos</Link></li>
在index.html里简单定义下.active:
<style>.active { color: red }</style>
手动刷新http://localhost:8080 因为 Webpack-dev-server 的热替换功能 不会作用于index.html。
点击 About 或 Repos,被触发的导航链接会变为红色:
除了站点导航的一级链接,站点里的大部分链接 一般并不需要知道是否被触发,如果将导航链接单独提取为一个组件,你就不用担心因为activeClassName和activeStyle属性的到处存在 而难以维护。
创建modules/NavLink.js,代码如下:
// modules/NavLink.js
import React from 'react'
import { Link } from 'react-router'
export default class NavLink extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Link {...this.props} activeClassName="active"/>
);
}
}
这里的…this.props点点点,使用了延展操作符(spread operator),它可以传递一组属性键值对给<Link/>
修改modules/App.js,导入./NavLink 并将<Link/>换成<NavLink/>
// NavLink
import NavLink from './NavLink';
// ...
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
六. URL 参数
来看下面的 URL:
/repos/reactjs/react-router
/repos/facebook/react
这些 URL 可以匹配如下这样的路由路径:
/repos/:userName/:repoName
其中:后面的部分为参数,它的具体值可以通过路由组件的this.props.params[name]属性获取到
首先,创建modules/Repo.js:
// modules/Repo.js
import React from 'react';
export default class Repo extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>{ this.props.params.repoName }</h2>
</div>
);
}
}
这里的this.props.params.repoName是为了获取路由路径——/repos/:userName/:repoName中repoName的具体值。
修改index.js,添加 Repo 路由,path属性指定了路由的 匹配规则,这里设为/repos/:userName/:repoName
// ...
// import Repo
import Repo from './modules/Repo'
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/about" component={About}/>
<Route path="/repos" component={Repos}/>
{/* Repo Route */}
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
</Router>
), document.getElementById('app'));
修改 modules/Repos.js,添加一些链接:
// ...
import { Link } from 'react-router'
export default class Repos extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>Repos</h2>
<ul>
<li><Link to="/repos/reactjs/react-router">React Router</Link></li>
<li><Link to="/repos/facebook/react">React</Link></li>
</ul>
</div>
);
}
}
测试下结果,首页确保你的静态服务器是开启状态 如果没有就运行npm start
访问http://localhost:8080/#/repos/可以看到:
点击链接 React Router 会渲染Repo组件:
可以看到Repo组件的内容为:react-router,正是通过this.props.params.repoName属性获取到路由匹配规则/repos/:userName/:repoName中参数:repoName的具体值,即 URL 中/repos/reactjs/react-router的 react-router 字段。
另外,路由 path 属性的常用 匹配规则(参考了阮一峰的 React-Router 教程资料)如下:
path | 匹配 | 说明 |
---|---|---|
path=”/hello/:userName” | /hello/nicholas /hello/jack | :paramName匹配 URL 的一个部分,直到遇到下一个/,?,#为止。这个路径参数可以通过this.props.params.paramName获取 |
path=”/hello(/:userName)” | /hello /hello/nicholas /hello/jack | ()表示 URL 的这个部分是可选的 |
path=”/hello/*” | /hello/nicholas /hello/nicholas/jack /hello/index.html | *匹配任意字符,直到模式里面的下一个字符为止,匹配方式为非贪婪模式 |
path=”/**/*.jpg” | /hello/test.jpg /react/hello/to/a.jpg | **匹配任意字符,直到遇到下一个/,?,#为止,匹配方式为贪婪模式 |
七. 多层嵌套路由
<router>还可以多层嵌套,上一节示例中当点击 Repos 的子导航链接 React Router 整个子导航区域会被内容渲染所替代,如果想查看 React 的内容,就必须点击浏览器回退按钮,这显然不利于用户体验。我们来改进下,让子导航始终存在,将内容部分放在导航下面显示
首先,在index.js中将Repo路由嵌套进Repos路由内部:
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
接着在modules/Repos.js中添加this.props.children属性,将Link组件换成之前我们封装的NavLink组件,链接的触发状态也有了:
// ...
// import NavLink
import NavLink from './NavLink'
export default class Repos extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>Repos</h2>
{/* 将 Link 换成 NavLink */}
<ul>
<li><NavLink to="/repos/reactjs/react-router">React Router</NavLink></li>
<li><NavLink to="/repos/facebook/react">React</NavLink></li>
</ul>
{ this.props.children }
</div>
);
}
}
ok 测试下,点击链接 React-Router 结果如下:
八. IndexRoute 组件
// modules/App.js
// ...
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
</ul>
{this.props.children}
</div>
);
}
// index.js
// ...
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/about" component={About}/>
<Route path="/repos" component={Repos}/>
</Route>
</Router>
), document.getElementById('app'));
上面代码,当访问 根路径/(即http://localhost:8080/#/) 时,页面并没有渲染任何子组件,因为此时App组件中的{this.props.children}获取到的值为undefined,如图:
我们可以用{this.props.children || <Home/>}来解决这个问题,在this.props.children为空时渲染Home组件,创建modules/Home.js:
// modules/Home.js
import React from 'react';
export default class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>Home</div>
}
}
在modules/App.js中增加{this.props.children || <Home/>}:
// modules/App.js
// ...
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
</ul>
{this.props.children || <Home/>}
</div>
);
}
上面的办法虽然能解决问题,但显然不优雅,应该将Home组件放到路由中。将modules/App.js中的{this.props.children || <Home/>}还原为{this.props.children}。
修改index.js,使用 IndexRoute 组件作为首页路由:
// index.js
// ...
import { Router, Route, hashHistory, IndexRoute } from 'react-router';
import Home from './modules/Home';
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
{/* 添加 IndexRoute 组件 */}
<IndexRoute component={Home}/>
<Route path="/about" component={About}/>
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
</Route>
</Route>
), document.getElementById('app'));
访问根路径http://localhost:8080,测试结果如图:
九. IndexLink 组件
你有没注意到,在我们的app中还缺少一个能切换渲染Home组件的导航链接?
尝试在modules/App.js中添加一条<NavLink/>,将to属性设为/根路径:
<li><NavLink to="/">Home</NavLink></li>
测试时你会发现无论点击哪个导航链接,Home 链接始终处于激活状态(如图,点击 About 后):
因为,当子路由处于激活状态时 父路由也会被激活,而/路径可以匹配任何子路由,解决办法是使用 IndexLink 组件,修改 modules/App.js,增加activeClassName属性:
// ...
import { IndexLink } from 'react-router';
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
{/* add here */}
<li><IndexLink to="/" activeClassName="active">Home</IndexLink></li>
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
</ul>
{this.props.children}
</div>
);
}
}
这里有个小问题,只有在精确匹配到根路由时,Home 链接的activeClassName属性才生效。
另一个方法是直接使用Link组件的onlyActiveOnIndex属性来达到同样效果,语法如下:
<li><Link to="/" activeClassName="active" onlyActiveOnIndex={true}>Home</Link></li>
之前我们已经将导航链接Link组件封装成了NavLink组件,这里就只需在NavLink组件上增加onlyActiveOnIndex属性,修改modules/App.js:
import React from 'react';
import NavLink from './NavLink';
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
{/* 增加 onlyActiveOnIndex 属性即可 */}
<li><NavLink to="/" onlyActiveOnIndex={true}>Home</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
</ul>
{this.props.children}
</div>
);
}
}
本篇示例源码:
react-router-demo-part2
下篇预告:
React-Router 中文简明教程(下),本文由 前端先生 原创,欢迎转载分享,但请注明出处。