美文网首页
2021 react-router v6 快速入门

2021 react-router v6 快速入门

作者: mudssky | 来源:发表于2021-11-26 11:28 被阅读0次

    使用官方的教学项目

    npx create-react-app router-tutorial
    

    安装 react-router 依赖

    cd router-tutorial
    npm add react-router-dom@6 history@5
    

    react-router-dom是浏览器端的基于react-router库的库,所以装了这个以后就不用再手动装react-router了

    修改App.js和 index.js到简单的样子

    //src/App.js
    export default function App() {
      return (
        <div>
          <h1>Bookkeeper!</h1>
        </div>
      );
    }
    
    // src/index.js
    import { render } from "react-dom";
    import App from "./App";
    
    const rootElement = document.getElementById("root");
    render(<App />, rootElement);
    

    然后启动项目,然后我们可以在基础上修改了。

    # probably this
    npm start
    
    # or this
    npm run dev
    
    #or
    yarn start
    

    01.BrowserRouter

    连接你的app到浏览器的URL。

    用BrowserRouter包裹在你的App的外面

    //src/index.js
    import { render } from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    import App from "./App";
    
    const rootElement = document.getElementById("root");
    render(
      <BrowserRouter>
        <App />
      </BrowserRouter>,
      rootElement
    );
    

    02.添加一些链接Link

    src/App.js里添加一些链接和全局导航。

    import { Link } from "react-router-dom";
    
    export default function App() {
      return (
        <div>
          <h1>Bookkeeper</h1>
          <nav
            style={{
              borderBottom: "solid 1px",
              paddingBottom: "1rem"
            }}
          >
            <Link to="/invoices">Invoices</Link> |{" "}
            <Link to="/expenses">Expenses</Link>
          </nav>
        </div>
      );
    }
    

    现在点击那些链接,你会发现地址栏会发生改变,也可以用前进后退在历史记录中移动

    03.添加一些路由

    • src/routes/invoices.jsx
    • src/routes/expenses.jsx
    //src/routes/expenses.jsx
    export default function Expenses() {
      return (
        <main style={{ padding: "1rem 0" }}>
          <h2>Expenses</h2>
        </main>
      );
    }
    
    //src/routes/invoices.jsx
    export default function Invoices() {
      return (
        <main style={{ padding: "1rem 0" }}>
          <h2>Invoices</h2>
        </main>
      );
    }
    

    接下来我们需要在index.js里面创建路由配置告诉app如何渲染不同的url

    //src/index.js
    import { render } from "react-dom";
    import {
      BrowserRouter,
      Routes,
      Route
    } from "react-router-dom";
    import App from "./App";
    import Expenses from "./routes/expenses";
    import Invoices from "./routes/invoices";
    
    const rootElement = document.getElementById("root");
    render(
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />} />
          <Route path="expenses" element={<Expenses />} />
          <Route path="invoices" element={<Invoices />} />
        </Routes>
      </BrowserRouter>,
      rootElement
    );
    

    04.嵌套路由

    我们注意到点击链接的时候,App中的布局消失了。只剩下Expenses或Invoices这两个路由指向的内容

    嵌套路由的作用就是共享部分UI

    我们需要两步操作实现这一点

    首先index.js里面对路由进行嵌套。这样两个组件就变成了App组件的子节点

    import { render } from "react-dom";
    import {
      BrowserRouter,
      Routes,
      Route
    } from "react-router-dom";
    import App from "./App";
    import Expenses from "./routes/expenses";
    import Invoices from "./routes/invoices";
    
    const rootElement = document.getElementById("root");
    render(
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />}>
            <Route path="expenses" element={<Expenses />} />
            <Route path="invoices" element={<Invoices />} />
          </Route>
        </Routes>
      </BrowserRouter>,
      rootElement
    );
    

    当路由拥有子节点的时候会发生两件事

    1. 路由的url嵌套 ("/" + "expenses" and "/" + "invoices")
    2. 子路由组件匹配的时候也会渲染父组件共享的部分

    接下来我们在App.jsx添加一个Outlet作为渲染子节点路由的地方

    //src/App.jsx
    import { Outlet, Link } from "react-router-dom";
    
    export default function App() {
      return (
        <div>
          <h1>Bookkeeper</h1>
          <nav
            style={{
              borderBottom: "solid 1px",
              paddingBottom: "1rem"
            }}
          >
            <Link to="/invoices">Invoices</Link> |{" "}
            <Link to="/expenses">Expenses</Link>
          </nav>
          <Outlet />
        </div>
      );
    }
    

    这下我们就可以在两个路由间切换保持共享的布局了。

    05.给Invoices路由添加数据

    我们模拟真实使用场景,给Invoices路由造点假数据

    // src/data.js
    let invoices = [
      {
        name: "Santa Monica",
        number: 1995,
        amount: "$10,800",
        due: "12/05/1995"
      },
      {
        name: "Stankonia",
        number: 2000,
        amount: "$8,000",
        due: "10/31/2000"
      },
      {
        name: "Ocean Avenue",
        number: 2003,
        amount: "$9,500",
        due: "07/22/2003"
      },
      {
        name: "Tubthumper",
        number: 1997,
        amount: "$14,000",
        due: "09/01/1997"
      },
      {
        name: "Wide Open Spaces",
        number: 1998,
        amount: "$4,600",
        due: "01/27/2998"
      }
    ];
    
    export function getInvoices() {
      return invoices;
    }
    

    然后我们修改invoices.jsx组件,获取并且渲染数据

    //src/routes/invoices.jsx
    import { Link } from "react-router-dom";
    import { getInvoices } from "../data";
    
    export default function Invoices() {
      let invoices = getInvoices();
      return (
        <div style={{ display: "flex" }}>
          <nav
            style={{
              borderRight: "solid 1px",
              padding: "1rem"
            }}
          >
            {invoices.map(invoice => (
              <Link
                style={{ display: "block", margin: "1rem 0" }}
                to={`/invoices/${invoice.number}`}
                key={invoice.number}
              >
                {invoice.name}
              </Link>
            ))}
          </nav>
        </div>
      );
    }
    

    06.添加一个不匹配路由

    我们可以发现,当我们输入一个没有分配地址的路由的时候,会显示空白页。

    实际上有一个好办法就是把这些不匹配的路由都导入一个404页面。

    我们添加一个"*"路由,这个路由会匹配所有没有匹配其他路由的路由

    // src/index.js
    <Routes>
      <Route path="/" element={<App />}>
        <Route path="expenses" element={<Expenses />} />
        <Route path="invoices" element={<Invoices />} />
        <Route
          path="*"
          element={
            <main style={{ padding: "1rem" }}>
              <p>There's nothing here!</p>
            </main>
          }
        />
      </Route>
    </Routes>
    

    07.读取url参数

    下面我们添加一些新组件,用于显示固定年份的invoice

    // src/routes/invoice.jsx
    export default function Invoice() {
      return <h2>Invoice #???</h2>;
    }
    

    然后我们在invoices路由下面添加这个子路由

    // src/index.js
    <Routes>
      <Route path="/" element={<App />}>
        <Route path="expenses" element={<Expenses />} />
        <Route path="invoices" element={<Invoices />}>
          <Route path=":invoiceId" element={<Invoice />} />
        </Route>
        <Route
          path="*"
          element={
            <main style={{ padding: "1rem" }}>
              <p>There's nothing here!</p>
            </main>
          }
        />
      </Route>
    </Routes>
    

    我们刚刚创建的路由是匹配 "/invoices/2005" and "/invoices/1998"这种格式的。

    然后我们还需要在invoices.jsx 添加一个outlet,不然显示不出来子路由的内容

    然后我们在invoice.jsx 文件中获取url参数

    // src/routes/invoice.jsx
    import { useParams } from "react-router-dom";
    
    export default function Invoice() {
      let params = useParams();
      return <h2>Invoice: {params.invoiceId}</h2>;
    }
    

    接着我们在data.js里面添加一个根据年份返回对应年份数据的函数

    //...
    export function getInvoices() {
      return invoices;
    }
    
    export function getInvoice(number) {
      return invoices.find(
        invoice => invoice.number === number
      );
    }
    

    然后我们就能用这个函数获取数据并且渲染出来了。

    import { useParams } from "react-router-dom";
    import { getInvoice } from "../data";
    
    export default function Invoice() {
      let params = useParams();
      let invoice = getInvoice(parseInt(params.invoiceId, 10));
      return (
        <main style={{ padding: "1rem" }}>
          <h2>Total Due: {invoice.amount}</h2>
          <p>
            {invoice.name}: {invoice.number}
          </p>
          <p>Due Date: {invoice.due}</p>
        </main>
      );
    }
    

    08.index路由

    这可能是react router 里面最难理解的概念。

    当我们浏览 invoices 路由的子路由内容,之后我们点击invoices路由的链接,我们发现右侧变成了空白。

    我们可以添加一个index路由解决这个问题

    // src/index.js
    
    <Routes>
      <Route path="/" element={<App />}>
        <Route path="expenses" element={<Expenses />} />
        <Route path="invoices" element={<Invoices />}>
          <Route
            index
            element={
              <main style={{ padding: "1rem" }}>
                <p>Select an invoice</p>
              </main>
            }
          />
          <Route path=":invoiceId" element={<Invoice />} />
        </Route>
        <Route
          path="*"
          element={
            <main style={{ padding: "1rem" }}>
              <p>There's nothing here!</p>
            </main>
          }
        />
      </Route>
    </Routes>
    

    接下来我们发现点击invoices路由的时候会默认显示index路由的内容而不是空白。

    index路由和其他路由不同的地方是它没有path属性,他和父路由共享同一个路径。

    下面几点可以帮助你理解这个概念

    • index路由渲染在父路由的outlet,而且路由地址和父路由相同
    • index路由在父路由匹配并且其他子路由不匹配的时候 匹配
    • index路由是一个父节点默认的子节点
    • index路由在用户还没有点击导航中的链接时渲染

    09.高亮激活的链接

    通常我们需要,特别是在导航列表里面,需要展示给用户当前激活的链接是哪个。

    西面我们把invoices.jsx中的Link换成NavLink

    import { NavLink, Outlet } from "react-router-dom";
    import { getInvoices } from "../data";
    
    export default function Invoices() {
      let invoices = getInvoices();
      return (
        <div style={{ display: "flex" }}>
          <nav
            style={{
              borderRight: "solid 1px",
              padding: "1rem"
            }}
          >
            {invoices.map(invoice => (
              <NavLink
                style={({ isActive }) => {
                  return {
                    display: "block",
                    margin: "1rem 0",
                    color: isActive ? "red" : ""
                  };
                }}
                to={`/invoices/${invoice.number}`}
                key={invoice.number}
              >
                {invoice.name}
              </NavLink>
            ))}
          </nav>
          <Outlet />
        </div>
      );
    }
    

    我们做了以下3件事

    1. 替换Link为NavLink

    2. 我们用函数改变样式

    3. 我们该百年了链接颜色通过NavLink传递过来的isActive属性

    我们也可以利用className做到一样的效果。

    // normal string
    <NavLink className="red" />
    
    // function
    <NavLink className={({ isActive }) => isActive ? "red" : "blue"} />
    

    10.url搜索参数

    搜索参数就类似于url参数,但是他们在url中所处的位置不同。

    不是由/分隔,他们出现在一个?之后,类似于这样的形式 "/login?success=1"or"/shoes?brand=nike&sort=asc&sortby=price

    react router 提供了 useSearchParams 用于读取和操作搜索参数。它有点像useState,不同点是useState是操作内存中的数据,

    而他是设置url 搜索参数中的state

    // routes/invoices.jsx
    import {
      NavLink,
      Outlet,
      useSearchParams
    } from "react-router-dom";
    import { getInvoices } from "../data";
    
    export default function Invoices() {
      let invoices = getInvoices();
      let [searchParams, setSearchParams] = useSearchParams();
    
      return (
        <div style={{ display: "flex" }}>
          <nav
            style={{
              borderRight: "solid 1px",
              padding: "1rem"
            }}
          >
            <input
              value={searchParams.get("filter") || ""}
              onChange={event => {
                let filter = event.target.value;
                if (filter) {
                  setSearchParams({ filter });
                } else {
                  setSearchParams({});
                }
              }}
            />
            {invoices
              .filter(invoice => {
                let filter = searchParams.get("filter");
                if (!filter) return true;
                let name = invoice.name.toLowerCase();
                return name.startsWith(filter.toLowerCase());
              })
              .map(invoice => (
                <NavLink
                  style={({ isActive }) => ({
                    display: "block",
                    margin: "1rem 0",
                    color: isActive ? "red" : ""
                  })}
                  to={`/invoices/${invoice.number}`}
                  key={invoice.number}
                >
                  {invoice.name}
                </NavLink>
              ))}
          </nav>
          <Outlet />
        </div>
      );
    }
    

    11.自定义行为

    接着上一节的程序,

    我们发现如果我们点击了过滤出来的链接,就不会保持过滤了,input会被清空。

    我们能够在点击新链接的时候保持查询字符串,只要我们组合 Navlink和useLocation,组成一个我们自己的QueryNavLink

    import { useLocation, NavLink } from "react-router-dom";
    
    function QueryNavLink({ to, ...props }) {
      let location = useLocation();
      return <NavLink to={to + location.search} {...props} />;
    }
    

    类似于 useSearchParams, useLocation 也会返回一个location告诉我们一些信息。就类似于下面的格式

    {
      pathame: "/invoices",
      search: "?filter=sa",
      hash: "",
      state: null,
      key: "ae4cz2j"
    }
    

    12.编程式导航

    有时我们需要更改URL,

    我们添加一个按钮,将invoice删除,然后导航到索引路由。

    首先我们在data.js里添加下面用于删除一个invoice的函数

    export function deleteInvoice(number) {
      invoices = invoices.filter(
        invoice => invoice.number !== number
      );
    }
    

    然后我们添加删除按钮

    // src/routes/invoice.jsx
    import { useParams, useNavigate } from "react-router-dom";
    import { getInvoice, deleteInvoice } from "../data";
    
    export default function Invoice() {
      let navigate = useNavigate();
      let params = useParams();
      let invoice = getInvoice(parseInt(params.invoiceId, 10));
    
      return (
        <main style={{ padding: "1rem" }}>
          <h2>Total Due: {invoice.amount}</h2>
          <p>
            {invoice.name}: {invoice.number}
          </p>
          <p>Due Date: {invoice.due}</p>
          <p>
            <button
              onClick={() => {
                deleteInvoice(invoice.number);
                navigate("/invoices");
              }}
            >
              Delete
            </button>
          </p>
        </main>
      );
    }
    

    相关文章

      网友评论

          本文标题:2021 react-router v6 快速入门

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