back back-top comments magnifier menu mobile right smile views

React-Router 中文简明教程(中)

Jean
Jean

react-router-part2

上一篇 React-Router 中文简明教程(上) 中讲了 如何创建一个简单的 react 路由Link 组件的使用嵌套路由,接着之前的内容,我们来继续 React-Router 之旅~

文章大纲

本篇示例源码:
react-router-demo-part2

五. 为 Link 组件设置触发状态

有时我们需要在导航链接上设置一个触发状态的样式,让用户知道当前在哪个路由下,比如某宝的底部导航:

img_0984

一种方法 是使用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
点击 AboutRepos,被触发的导航链接会变为红色:

react-router-part2-result1

除了站点导航的一级链接,站点里的大部分链接 一般并不需要知道是否被触发,如果将导航链接单独提取为一个组件,你就不用担心因为activeClassNameactiveStyle属性的到处存在 而难以维护。

创建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/:repoNamerepoName的具体值。

修改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-part2-result2

点击链接 React Router 会渲染Repo组件:

react-router-part2-result3

可以看到Repo组件的内容为:react-router,正是通过this.props.params.repoName属性获取到路由匹配规则/repos/:userName/:repoName中参数:repoName的具体值,即 URL 中/repos/reactjs/react-routerreact-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 结果如下:

react-router-part2-result4

八. 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,如图:

react-router-part2-result5

我们可以用{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,测试结果如图:

react-router-part2-result6

九. IndexLink 组件

你有没注意到,在我们的app中还缺少一个能切换渲染Home组件的导航链接?
尝试在modules/App.js中添加一条<NavLink/>,将to属性设为/根路径:

<li><NavLink to="/">Home</NavLink></li>

测试时你会发现无论点击哪个导航链接,Home 链接始终处于激活状态(如图,点击 About 后):

react-router-part2-result7

因为,当子路由处于激活状态时 父路由也会被激活,而/路径可以匹配任何子路由,解决办法是使用 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-part2-result8

本篇示例源码:
react-router-demo-part2

下篇预告:
React-Router 中文简明教程(下),本文由 前端先生 原创,欢迎转载分享,但请注明出处。

0条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

扫描二维码分享到微信