React-Router 03 Link与Redirect

React-Router 03 Link与Redirect

链接组件也不简单,动态解析路由还挺有趣。

这两个组件,实际上是用于路由跳转,也就是更改浏览器的URL。在URL发生变化之后,Router组件和Route组件才会发挥作用。

Link

在第一篇文章中就使用了Link组件,看一下当时的代码:

export const Nav = () => (
    <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/contact">Contact</Link></li>
    </ul>
);

Link组件正常状态下渲染出来的是一个HTML A标签。其中有一个重要的属性是to,Link的常用属性如下:

  1. to,用于指示这个Link组件对应的最终渲染的href属性,即超链接指向哪里。
  2. replace,一个布尔值,默认是false,如果设置为true,则点击链接会在history栈覆盖当前的地址,而不是向栈中增加一条路径。
  3. component,需要传入一个组件或者函数,用于替换Link实际渲染的东西
  4. 其他HTML的a标签允许附加的属性,会渲染到最后的a标签上,比如id,className,title等属性。

NavLink相比Link,是一个特殊版本的Link,加上了一些关于样式的控制。

to属性

to属性可以接受如下三个参数:

  1. 可以是一个字符串,包含#和查询串,比如/contract?supplier=3
  2. 是一个对象,例如:<Link to={{pathname: "/courses", search: "?sort=name", hash: "#the-hash", state: { fromDashboard: true }}}/>,前三个属性很好理解,最后一个state表示存入location的state,即历史帧。
  3. 可以传入一个函数,这个函数会被传入location作为参数,经过处理之后返回一个字符串或者符合第二条要求的对象,例如:<Link to={location => ({ ...location, pathname: "/courses" })} />

使用to属性,就可以灵活的根据逻辑来动态生成链接。

NavLink

这个东西加上了一些对于样式的控制和路由的匹配,看起来像是Route和Link的结合,Route来匹配to属性,Link用来渲染链接。来简单看一下属性:

  1. to属性,这个属性接受的值和Link一样,但是特别的是还会作为匹配的路径
  2. className,这个就是React的标准类名,会被原样渲染到最终的元素上,接受一个字符串或者返回字符串的函数,这都是老套路了。
  3. style,这也是符合React标准的属性,接受一个对象,其中的CSS属性名属性使用驼峰样式。
  4. activeStyle,这个是当组件处于active状态的时候,使用的样式,什么叫这个组件处于active状态呢,看下一个属性
  5. isActive,接受布尔值或者函数返回布尔值,函数的两个参数为match和location,如果匹配不成功,match为null,一般用来判断链接的状态。
  6. exact,这个属性为true的时候,activeStyle只有在精确match的时候才被应用

其实这个用起来很简单,比如:

<NavLink style={{textDecoration:"none"}} activeStyle={{"color":"red"}} to="/contact">Contact</NavLink>

这个NavLink既有链接的功能,又根据/contact来进行匹配,如果当前不是/contact地址,就不会显示红色,是contact地址则会显示红色。
如果加上isActive={()=>false},则这个链接就不会应用activeStyle

一般如果采取前端样式库比如MUI的时候,不会单独使用NavLink。

Redirect

这个组件很有意思,一旦成功渲染,就会将history当前栈改成to属性,模仿301重定向的行为。

to属性就是目标属性,即要跳转的地方。push属性如果设置为true,则不覆盖,而是向history栈中加入新的地址栈。from属性要搭配Switch组件来使用,下边会详述。

先来写一个简单的组件试验一下,比如在访问/home路径后,三秒中之后跳转到首页

// Switch中加一条路由
<Route path="/home" component={RedirectToHome}/>

// RedirectToHome组件:
export class RedirectToHome extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            time: 0,
            timeId: 0,
        }
    }

    componentDidMount() {
        this.setState({timeId: setInterval(() => this.setState({time: this.state.time + 1}), 1000)});
    }

    render() {
        if (this.state.time === 3) {
            clearInterval(this.state.timeId);
            return <Redirect to="/"/>;
        } else {
            return <p>{3 - this.state.time} 秒后跳转</p>;
        }
    }
}

这样写完之后,只要访问/home路径,倒数三秒后就会被重定向到首页。

来稍微看一下from属性。from属性需要搭配Switch来使用,相当于先匹配from属性,匹配成功的话,跳转到to属性中的地址,这中间还能够传递参数,来继续改造一下:

<Switch>
    <Route path="/about" render={(props) => (<About {...props} myattr={"saner"}/>)}/>
    <Route path="/contact" children={(props) => <div>{props.match ? "是contact路径" : "不是contact路径"}</div>}/>
    <Redirect from="/auser/:username" to="/user/:username"/>
    <Route path="/user/:user" component={User}/>
    <Route path="/" component={Home}/>
</Switch>

这个时候,如果路径是/auser/saner,会跳转到/user/saner路径,此时就会再匹配User组件。

可以发现,此时的Redirect其实也是Route和Redirect的综合体。

动态路由离散式声明

这里来看一个高阶的技巧,目前我们匹配user是直接在Route中声明了变量,比如/user/:user,虽然变量部分是动态,但这依然整体上是一种静态的声明,即把所有/user/之后的内容都当做了user变量的内容。

如果是纯粹的动态解析,那么在匹配了/user之后,应该将后边的内容再去进行动态解析和操作,此时就可以用一个Route组件返回一个Route组件来执行操作。

步骤如下:

先把Route修改为如下:

<BrowserRouter>
    <Nav/>
    <Route path="/about" render={(props) => (<About {...props} myattr={"saner"}/>)}/>
    <Route path="/contact" children={(props) => <div>{props.match ? "是contact路径" : "不是contact路径"}</div>}/>
    <Route path="/user" component={User}/>
    <Route path="/" exact component={Home}/>
</BrowserRouter>

注意,动态路由不能和Switch搭配使用,因为等于精确匹配,没有意义了。

这是让Route只匹配/user开始的路径,至于匹配的路径,在User中可以继续定义,就像接力一样:

export class User extends React.Component {

    constructor(props, context) {
        super(props, context);
    }

    render() {
        const match = this.props.match;

        return (
            <div>
                <Route path={`${match.url}/:user`} component={UserDetail} />
            </div>
        );
    }
}

这里可以看到,User返回了一个Route,其中的path带上了从上一级传下来的match.url,然后拼接上了:user变量,如果此时URL符合/user/***的内容的话,***的内容就会以user变量的形式交给UserDetail去渲染,在其中还可以接力继续判断内容。

例子

这里我们使用这个方式来完成一个比较复杂的逻辑,这个逻辑如下:

  1. 用户访问/user,显示无用户名
  2. 用户访问/user/***,这个***如果是saner以外的字样,显示“用户不是三鹅”,不管之后还没有其他URL。
  3. 用户是三鹅的时候,路径为/user/saner,显示“三鹅你忘记输入内容了”
  4. 用户是三鹅的时候,路径为/user/saner/***,把***的部分打印出来。

第一步

我们一层一层的来,首先需要匹配/user路径,那么先写一个Route如下:

    <Route path="/user" component={User}/>

第二步

这个匹配成功之后,匹配的match对象会发送到User组件中,我们在User组件中再做判断:

export class User extends React.Component {

    constructor(props, context) {
        super(props, context);
    }

    render() {
        const match = this.props.match;
        return (
            <div>
                <Route path={`${match.url}/:user`} component={UserDetail}/>

                <Route path={`${match.url}`} exact render={() => <p>无用户名</p>}/>
            </div>
        );
    }
}

这一步就有点搞了,首先我们要清楚,此时的match.url是/user,在拼接上:user之后,返回的Route组件中的path属性是/user/:user。此时如果实际的路径中没有user变量,那么会落到下边的带有exact属性的Route组件中,就会显示无用户名。

如果实际路径中有user变量的部分,我们把这个路径又交给了UserDetail组件。

第三步

UserDetail组件的逻辑比较类似:

class UserDetail extends React.Component {

    constructor(props, context) {
        super(props, context);
    }

    render() {
        const {match} = this.props;
        if (match.params.user === "saner") {
            return (<>
                <Route path={`${match.url}/:gugugu`} render={(props) => {
                    console.log(props.match.params.gugugu);
                    return props.match.params.gugugu ? <p>最后的是{props.match.params.gugugu}</p> :
                        <p>没有内容</p>;
                }}/>
                <Route path={`${match.url}`} exact render={()=><p>三鹅你忘记输入内容了。</p>} />
            </>)
        } else {
            return (
                <p>用户不是三鹅</p>
            )
        }
    }
}

这里要清醒的知道,此时的match.url一定是一个/user/***的形式,否则进不来UserDetail组件。我们要做的是判断一下这个***是不是saner,如果不是,就显示“用户不是三鹅”,不用再去匹配之后的路径。

当用户是saner的时候,逻辑和User组件是一样的,继续匹配其后的路径,如果其后没有路径,就匹配到带有exact属性的Route组件中。

这种逻辑,如果试图用单独的Route来定义,是不太好写的,使用此种方式,一点一点解析动态的URL,能够实现很复杂的逻辑。当然一般情况下不会这么动态,肯定还是要对URL有所规划才行。

LICENSED UNDER CC BY-NC-SA 4.0
Comment