美文网首页
关于Reac-router:V3到V4版本的变更

关于Reac-router:V3到V4版本的变更

作者: pansly | 来源:发表于2019-02-24 20:12 被阅读0次

    一、React-router V3和V4版本核心区别

    React Router 3以及早起版本是将路由看成是一个整体的单元,与别的组件是分离的,一般会单独放到一个 router 文件中,对其进行集中式管理;并且,布局和页面的嵌套由路由的嵌套所决定。可以归纳为以下几点:

    • 集中式 router
    • 通过 <Route> 嵌套,实现 Layout 和 page 嵌套
    • Layout 和 page 组件 是作为 router 的一部分

    下面看一段代码体验下V3版本Router设置

    import React from "react";
    import { render } from "react-dom";
    import { Router, Route, IndexRoute, Link, browserHistory } from "react-router";
    
    const PrimaryLayout = props =>
     <div className="primary-layout">
       <header>Our React Router 3 App</header>
       <ul>
         <li>
           <Link to="/">Home</Link>
         </li>
         <li>
           <Link to="/user">User</Link>
         </li>
       </ul>
       <main>
         {props.children}
       </main>
     </div>;
    
    const HomePage = () => <h1>Home Page</h1>;
    const UsersPage = () => <h1>User Page</h1>;
    
    const App = () =>
     <Router history={browserHistory}>
       <Route path="/" component={PrimaryLayout}>
         <IndexRoute component={HomePage} />
         <Route path="/user" component={UsersPage} />
       </Route>
     </Router>;
    
    render(<App />, document.getElementById("root"));
    

    在V4版本中 React Router 提供核心路由功能,但是你不需要直接安装 react-router;
    如果你写浏览器端应用,你应该安装 react-router-dom;
    如果你写 React Native 应用,你应该安装 react-router-native;
    当你安装 react-router-dom 或 react-router-native 时,都会将 react-router 作为依赖安装。

    上面的例子在V4版本配置如下:

    import React from "react";
    import { render } from "react-dom";
    import { BrowserRouter, Route, Link } from "react-router-dom";
     
    const PrimaryLayout = () =>
      <div className="primary-layout">
        <header>Our React Router 4 App</header>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/User">User</Link>
          </li>
        </ul>
        <main>
          <Route path="/" exact component={HomePage} />
          <Route path="/user" component={UsersPage} />
        </main>
      </div>;
     
    const HomePage = () => <h1>Home Page</h1>;
    const UsersPage = () => <h1>User Page</h1>;
     
    const App = () =>
      <BrowserRouter>
        <PrimaryLayout />
      </BrowserRouter>;
     
    render(<App />, document.getElementById("root"));
    

    通过不同配置,再次分析React Router 4 与之前的版本的区别:最大的不同便是 router 在项目中的位置:

    • v4 的版本则将路由进行了拆分,将其放到了各自的模块中,不再有单独的 router 模块,充分体现了组件化的思想;另外,<BrowserRouter> 的使用与之前作为 history 属性传入的方式也不同了。

    • v4 的这种方式让路由和组件之间的关系变得特别好理解,可以将 Route 就当做 component 组件一样使用,只不过此时的 URL 是其对应的 path;当 path 匹配时,则会渲染 Route 所对应的组件内容。

    二、包含式路由与exact

    在之前的版本中,在 Route 中写入的 path,在路由匹配时是独一无二的,而 v4 版本则有了一个包含的关系:如匹配 path="/users" 的路由会匹配 path="/"的路由,在页面中这两个模块会同时进行渲染。因此,v4中多了 exact 关键词,表示只对当前的路由进行匹配。

    // 当匹配 /users 时,会同时渲染 UsersMenu 和 UsersPage
    const PrimaryLayout = () => (
      <div className="primary-layout">
        <header>
          Our React Router 4 App
          <Route path="/users" component={UsersMenu} />
        </header>
        <main>
          <Route path="/" exact component={HomePage} />
          <Route path="/users" component={UsersPage} />
        </main>
      </div>
    )
    
    

    三、独立路由:Switch

    如果想要只匹配一个路由,除了 exact 属性之外,还可以使用 Swtich 组件。

    const PrimaryLayout = () => (
      <div className="primary-layout">
        <PrimaryHeader />
        <main>
          <Switch>
            <Route path="/" exact component={HomePage} />  // 必须加上 exact,要不然 /users 也会匹配到该路由
            <Route path="/users/add" component={UserAddPage} />
            <Route path="/users" component={UsersPage} />
            <Redirect to="/" />
          </Switch>
        </main>
      </div>
    )
    
    

    采用 <Switch>,只有一个路由会被渲染,并且总是渲染第一个匹配到的组件。因此,在第一个路由中,还是需要使用 exact,否则,当我们渲染 '/users' 或 '/users/add' 时,只会显示匹配 '/' 的组件(PS:如果不使用 <Switch>,当我们不使用 exact 时,会渲染匹配的多个组件)。所以,将 '/user/add' 路由放在 '/users' 之前更好,因为后者包含了前者,当然,我们也可以同样使用 exact,这样就可以不用关注顺序了。

    再来说一下 <Redirect> 组件,单独使用时,一旦当路由匹配到的时候,浏览器就会进行重定向跳转;而配合 <Switch> 使用时,只有当没有路由匹配的时候,才会进行重定向。例如,上面的例子,地址栏输入 '/test' 时,则会跳转到 '/',渲染 HomePage 页面。

    四、"Index Routes" 和 "Not Found"

    在 v4 的版本中废弃了 <IndexRoute>,而该用 <Route exact> 的方式进行代替。如果没有匹配的路由,也可通过 <Redirect> 来进行重定向到默认页面或合理的路径。

    五、重定向路由

    v4去掉 <IndexRedirect>了。要实现重定向可以使用<Redirect>组件。

    // v3
    <Route path="/" component={App}>
      <IndexRedirect to="/welcome" />
    </Route>
    
    // v4
    <Route exact path="/" render={() => <Redirect to="/welcome" component={App} />} />
    //Switch限定只渲染第一个匹配的 Route
    <Switch>
      <Route exact path="/" component={App} />
      <Route path="/login" component={Login} />
      <Redirect path="*" to="/" />  //当以上的path均不匹配时,重定向到'/'
    </Switch>
    

    六、嵌套路由(Nested Routing)

    v4版本以前,我们可能会用到嵌套路由,尤其是当页面中有导航栏时。比如:

    <Route path='parent' component={Parent}>
      <Route path='child' component={Child} />
      <Route path='other' component={Other} />
    </Route>
    

    当嵌套的内存路由匹配时,外层路由和内层路由对应的组件都会被渲染出来,而且子组件会通过props.children传递给父组件,也就是子组件会渲染到父组件 render 方法中{this.props.children}所在的位置。
    v4版本时,就不需要在 Route 配置的地方嵌套好几个 Route,也不需要使用 this.props.children 指明子组件渲染的位置。只需要在父组件相应的位置添加 Route 即可。就跟嵌套一个 div 一样。

    <Route path='parent' component={Parent} />
    const Parent = () => (
      <div>
        //......
        <Route path='child' component={Child} />
        <Route path='other' component={Other} />
      </div>
    )
    

    七、Match

    props.match 包含4个属性match.paramsmatch.isExactmatch.pathmatch.url

    看一下 <UserProfile />match 属性:

    image

    1)match.path vs match.url

    当没有参数的时候,match.pathmatch.url 是一样的,而当有参数的时候,两者就有区别了:

    • match.path:是指写在 <Route> 中的 path 参数;
    • match.url:是指在浏览器中显示的真实 URL。
    const UserSubLayout = ({ match }) => {
        console.log(match.path)   // output: "/users"
        console.log(match.url)  // output: "/users"
        return (
          <div className="user-sub-layout">
            <aside>
              <UserNav />
            </aside>
            <div className="primary-content">
              <Switch>
                <Route path={match.path} exact component={BrowseUserTable} />
                <Route path={`${match.path}/:userId`} component={UserProfilePage} />
              </Switch>
            </div>
          </div>
        )
      }
    
    const UserProfilePage = ({match}) => {
        console.log(match.path); // output: "/users/:userId"
        console.log(match.url); // output: "/users/bob"
        return <UserProfile userId={match.params.userId} />
    }
    
    

    作者强烈建议在写路由路径时使用 match.path,因为使用 match.url 最终会产生不可预料的场景,如下面这个例子:

    const UserComments = ({ match }) => {
        console.log(match.params);  // output: {}
        return <div>UserId: {match.params.userId}</div>
    }
    
    const UserSettings = ({ match }) => {
        console.log(match.params);  // output: {userId: "5"}
        return <div>UserId: {match.params.userId}</div>
    }
    
    const UserProfilePage = ({ match }) => (
      <div>
        User Profile:
        <Route path={`${match.url}/comments`} component={UserComments} />
        <Route path={`${match.path}/settings`} component={UserSettings} />
      </div>
    )
    
    
    • 当访问 '/users/5/comments' 时渲染 'UserId: undefined';
    • 当访问 '/users/5/settings' 时渲染 'UserId: 5'。

    为什么会 match.path 能够正常渲染,而使用 match.url 则不能呢?造成这种区别的原因是由于 {${match.url}/comments} 相当于硬编码 {'/users/5/comments'},在路径中并没有参数,只有一个写死的 5,这样,子模块便无法获取到 match.params 参数,因此,便不能正常渲染。

    match.path 可用于构造嵌套的 <Route>,而 match.url 可用于构造嵌套的 <Link>

    2)如何避免 Match 的冲突?

    考虑这样一种情况:如果我们想要通过 '/users/add' 和 '/users/5/edit' 来对用户进行添加和编辑,但是在前面的例子中,我们知道 users/:userId 已经指向了 UserProfilePage,那按照之前的例子,是否意味着 users/:userId 需要指向一个可以同时实现编辑和预览功能的子页面模块?未必如此,因为 edit 和 profile 共享一个子页面,则可以通过以下方式进行实现:

    const UserSubLayout = ({ match }) => (
      <div className="user-sub-layout">
        <aside>
          <UserNav />
        </aside>
        <div className="primary-content">
          <Switch>
            <Route exact path={props.match.path} component={BrowseUsersPage} />
            <Route path={`${match.path}/add`} component={AddUserPage} />
            <Route path={`${match.path}/:userId/edit`} component={EditUserPage} />
            <Route path={`${match.path}/:userId`} component={UserProfilePage} />
          </Switch>
        </div>
      </div>
    )
    
    

    将 add 和 edit 页面放在 profile 之前,这样就可以实现按需匹配,如果将 profile 路径放在第一位的话,那么当访问 add 页面时,则会匹配 profile 页面,因为 add 匹配了 :userId

    还有一种替代方法,可以将 profile 放在第一位:采用正则(path-to-regexp)对路径进行约束,如${match.path}/:userId(\\d+),这样 :userId 只能为 number 类型,则访问 /users/add 路径时便不会产生冲突。

    八、其它

    文中,作者最后自行实现了一个授权路由,另外,他还提到了React Router v4 中的其它部分,如 <Link> vs <NavLink>、URL Query Strings 以及 Dynamic Routes。

    这里我比较感兴趣的是 URL 查询,因为在项目开发中,我需要通过 URL 的查询字符串来实现一些功能。如 URL的查询字符串为 /users?bar=baz:

    • 在之前的版本中,可以通过 this.props.location.query.bar 进行获取(React Router 使用教程 );
    • 在 v4 中,首先看一下 this.props.location
    image

    由此可见,v4 版本中的 query 参数已经不见了,也就意味着,在该版本中,已经无法获得 URL 的查询字符串了。但是,我们却可以获取到 search 字段,只不过需要我们自行对其进行处理。

    备注:
    对于 React Router v2/v3 版本的学习,阮老师的文章 React Router 使用教程 写得通俗易懂,而 v4 版本则可直接去看 官网的资源

    另外:给大家推荐了一个在线 react 编译器 stackblitz

    相关文章

      网友评论

          本文标题:关于Reac-router:V3到V4版本的变更

          本文链接:https://www.haomeiwen.com/subject/kgwbyqtx.html