推荐一个 视频 https://www.youtube.com/watch?v=zEQiNFAwDGo

V6主要变动概览

  1. 重命名为
  2. 的新特性变更。
  3. 嵌套路由变得更简单。
  4. 用useNavigate代替useHistory。
  5. 新钩子useRoutes代替react-router-config。
  6. 大小减少:从20kb到8kb

使用方法参见 React-Router v6

Switch命名为Routes

  1. // v5
  2. <Switch>
  3. <Route exact path="/"><Home /></Route>
  4. <Route path="/profile"><Profile /></Route>
  5. </Switch>
  6. // v6
  7. <Routes>
  8. <Route path="/" element={<Home />} />
  9. <Route path="profile/*" element={<Profile />} />
  10. </Routes>

v6版本没有导出Switch,如果引入,会有提示错误
image.png

Route渲染方式改变

component/render的渲染方式更换成element形式
注意:

  • element中是JSX形式 ```javascript

//v6

} /> }/> }/>

  1. <a name="ZmPXR"></a>
  2. ## 嵌套路由更便捷
  3. - <Route children> 已更改为接受子路由。
  4. - 比<Route exact><Route strict>更简单的匹配规则。
  5. - <Route path> 路径层次更清晰。
  6. <a name="ga7gK"></a>
  7. ### 路径匹配更精准人性
  8. ```javascript
  9. <Route path="/about/:id" element={<About/>}/>
  10. <Route path="/about/books" element={<Dashboard/>}/>

当路径是/about/123会匹配到About
当路径是/about/books会匹配到Dashboard
v5中需要exacth和strict来进行精准匹配

v6中的所有路径匹配都将忽略URL上的尾部”/“。实际上,已被删除并且在v6中无效。这并不意味着您不需要使用斜杠。

在v5版本之前的路径,存在路由歧义

  1. 当前路径:”/users”,则将跳转
  2. 当前路径:”/users/“,则将跳转

React Router v6修复了这种歧义,取消了尾部”/“:

  1. 当前路径:”/users”,则将跳转
  2. 当前路径:”/users”,则将跳转

其形式更像命令行cd的用法

// 当前路径为 /app/dashboard 

<Link to="stats">               // <a href="/app/dashboard/stats">
<Link to="../stats">            // <a href="/app/stats">
<Link to="../../stats">         // <a href="/stats">
<Link to="../../../stats">      // <a href="/stats">

// 命令行当前路径为 /app/dashboard
cd stats                        // pwd is /app/dashboard/stats
cd ../stats                     // pwd is /app/stats
cd ../../stats                  // pwd is /stats
cd ../../../stats               // pwd is /stats

嵌套更方便-脱离useRouteMatch

V5路由回顾中,我们对useRouteMatch用法做过介绍。
V5版本中,需要useRouteMatch来进行匹配,进而根据匹配的url来进行下一步的嵌套渲染。

//v5
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch,
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" render={()=><div>Home组件</div>} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </BrowserRouter>
  );
}

function Profile() {
  let { path, url } = useRouteMatch();
  console.log('Profile useRouteMatch',useRouteMatch('/profile/:id'));

  return (
    <div>
      <nav>
        <Link to={`${url}/me`}>My Profile</Link>
      </nav>
      <Switch>
        <Route path={`${path}/me`} render={()=> (<div>第一个子组件</div>)} />
        <Route path={`${path}/:id`} render={()=>(<div>第二个子组件</div>)}/>
      </Switch>
    </div>
  );
}

export default App

image.png

V6中,你可以删除字符串匹配逻辑。不需要任何useRouteMatch()!
原因是 V6中Link时的处理跟V5不一样 参见 路径匹配更精准人性

注意下 profile/*

function App() {
  return (
    <Router>
      <Routes>
        <Route exact path="/" element={<Home />} />
        // 注意这里路径是profile/*
        <Route path="profile/*" element={<Profile />}></Route>
      </Routes>
    </Router>
  );
}

function Home() {
  return <>Home V6</>;
}

function Profile() {
  // 这里不需要useRouteMatch
  // let { path, url } = useRouteMatch();
  // console.log("Profile useRouteMatch", useRouteMatch());

  return (
    <div>
      <nav>
        <Link to={`me`}>My Profile</Link>
      </nav>
      <Routes>
        <Route path={`me`} element={<Profile1 />} />
        <Route path={`:id`} element={<Profile2 />} />
      </Routes>
    </div>
  );
}
function Profile1() {
  return <>第一个子组件</>;
}
function Profile2() {
  return <>第二个子组件</>;
}

新的API:Outlet

还是上面的例子,outlet相当于props.children,会将嵌套路由中的子组件渲染出来
可太6了

function App() {
  return (
    <Router>
      <Routes>
        <Route exact path="/" element={<Home />} />
        {/**这里路径 可以是profile 也可以是profile/*  */}
        <Route path="profile" element={<Profile />}>
          <Route path={`me`} element={<Profile1 />} />
          <Route path={`:id`} element={<Profile2 />} />
        </Route>
      </Routes>
    </Router>
  );
}

function Home() {
  return <>Home V6</>;
}

function Profile() {

  return (
    <div>
      <nav>
        <Link to={`me`}>My Profile V6</Link>
      </nav>
      {/* <Routes>
        <Route path={`me`} element={<Profile1 />} />
        <Route path={`:id`} element={<Profile2 />} />
      </Routes> */}

      {/**这里相当于props.children 将Profile中的children渲染出来 */}
      <Outlet />
    </div>
  );
}
function Profile1() {
  return <>第一个子组件V6</>;
}
function Profile2() {
  return <>第二个子组件V6</>;
}

export default App;

useRoutes代替react-router-config

需要注意两点:

定义router.js


import Home from "./pages/home";
import About from "./pages/about";
import Dashboard from "./pages/dashborad";
import Detail from "./pages/detail";


const routes = [
    {
      path: "/",
      element: <Home/>,
    },
  {
    path: "about",
    element: <About />,
    //此处添加嵌套路由
     children: [
        {
          path: "detail",
          element: <Detail />,
        },
      ],
    },
    {
      path: "dashboard",
      element: <Dashboard />,
    },
    // 重定向
    { path: "/login", redirectTo: "/" },
    // 404找不到
    { path: "*", element: <NotFound /> },
  ];

  //将路由表数组导出
  export default routes;
const App = () => {
  let routes = useRoutes(router);
  return routes;
};
console.log('APP',App);

const AppWrapper = () => {
  return (
    <Router>
      <App />
    </Router>
  );
};
export default AppWrapper;

(重定向和嵌套路由)

useNavigate代替useHistory

// v5
import { useHistory } from 'react-router-dom';

function MyButton() {
  let history = useHistory();
  function handleClick() {
    history.push('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

history.push()将替换为navigation()

// v6
import { useNavigate } from 'react-router-dom';

function MyButton() {
  let navigate = useNavigate();
  function handleClick() {
    navigate('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

这里注意下,history的用法也被替换

// v5
history.push('/home');
history.replace('/home');

// v6
navigate('/home');
navigate('/home', {replace: true});

大小减少:从20kb到8kb

代码体检减少到8kb,主要核心代码react-router-dom大概一千多行,react-router中400多行

image.png

useBlocker

import {
   useCallback
} from 'react';
import {
   useBlocker,
   useLocation,
   useNavigate
} from 'react-router';

const locationProperties = ['pathname', 'search', 'state'];

function isSameLocation(location1, location2) {
   return locationProperties.every((property) => location1[property] === location2[property]);
}

export default function NavigationGuard() {
   const location = useLocation();
   const navigate = useNavigate();

   const blocker = useCallback(
      ({
         action,
         location: nextLocation,
         retry
      }) => {
         switch (action) {
            case 'PUSH':
            case 'REPLACE': {
               retry();
               return;
            }
            case 'POP': {
               if (isSameLocation(nextLocation, location)) {
                  retry();
                  return;
               }

               const answer = confirm('Are you sure you want to leave this page?');

               if (answer) {
                  navigate('/');
               }

               return;
            }
         }
      },
      [dispatch, location, navigate],
   );

   useBlocker(blocker);

   return null;
}

References

https://reactrouter.com/docs/en/v6/getting-started/tutorial
https://juejin.cn/post/6844904096059621389#heading-8