二十二、React Router 5

郁子大约 7 分钟约 2008 字笔记React18React Router 5李立超

(一)React Router 简介

  • url 地址和组件进行映射,当用户访问某个地址时与其对应的组件会自动挂载
  • 使用步骤
    • 引入 react-router-dom
    • index.js 中引入 BrowserRouter 组件
    • BrowserRouter 设置为根组件

1. src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Router>
    <App />
  </Router>,
);

2. src/App.js

  • 使用 Router 来映射路由地址和组件

1)属性

  • path 映射的 url 地址
  • component 要挂载的组件
  • exact 路径是否开启完整匹配,默认 false

2)当 Route 的路径被访问时,对应组件就会自动挂载

  • 默认情况下 Route 不是严格匹配,只要 url 地址的头部和 path 一致,组件就会挂载,不会检查子路径
import { Route } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";

function App() {
  return (
    <div>
      App组件
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </div>
  );
}

export default App;

3. src/components/Home.jsx

import React from "react";

const Home = () => {
  return (
    <div>
      <h1>主页有非常好的内容</h1>
    </div>
  );
};

export default Home;

4. src/components/About.jsx

import React from "react";

const About = () => {
  return (
    <div>
      <h2>关于我们,其实是师徒四人</h2>
      <ul>
        <li>唐三藏</li>
        <li>孙悟空</li>
        <li>猪八戒</li>
        <li>沙和尚</li>
      </ul>
    </div>
  );
};

export default About;

1. src/App.js

import { Route } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Menu from "./components/Menu";

function App() {
  return (
    <div>
      App组件
      <Menu />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </div>
  );
}

export default App;

2. src/components/Menu.jsx

  • 使用 react router 时一定不要使用 a 标签创建超链接
    • 会自动向服务器发送请求并重新加载页面
  • 使用 Link 创建超链接
  • NavLinkLink 相似,只是可以指定激活样式
import React from "react";
import { Link, NavLink } from "react-router-dom";
import classes from "./Menu.module.css";

const Menu = () => {
  return (
    <div>
      <ul>
        <li>
          {/* <a href="/">主页</a> */}

          {/* <Link to="/">主页</Link> */}

          <NavLink
            exact
            to="/"
            // activeClassName={classes.active}
            activeStyle={{
              textDecoration: "underline",
            }}
          >
            主页
          </NavLink>
        </li>
        <li>
          {/* <a href="/about">关于</a> */}

          {/* <Link to="/about">关于</Link> */}

          <NavLink
            exact
            to="/about"
            // activeClassName={classes.active}
            activeStyle={{
              textDecoration: "underline",
            }}
          >
            关于
          </NavLink>
        </li>
      </ul>
    </div>
  );
};

export default Menu;

3. src/components/Menu.module.css

a:link,
a:visited {
  color: orange;
  text-decoration: none;
}
a:hover,
a.active {
  color: red;
  text-decoration: underline;
}

(三)两种 Router

  • 没有经过服务器,可以正常跳转

2.刷新页面或普通链接跳转

  • 向服务器发送请求加载数据,此时的请求没有经过 react router ,返回 404

3.解决方案

1)使用 HashRouter

  • 服务器不会判断 hash 值,请求将由 react router 处理
  • 地址栏带 # ,对 SEO 不友好

2)修改服务器配置,将所有请求都转发到 index.html

location / {
  root html;
  #index index.html index.htm;
  try_files $uri /index.html;
}
  • HashRouter
    • 通过 url 地址中的 hash 值来对地址进行匹配
  • BrowserRouter
    • 通过 url 地址进行组件的跳转,使用过程和普通 url 地址没有区别

4. src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
// import { HashRouter as Router } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Router>
    <App />
  </Router>,
);

(四)路由组件

1.component

  • 用于指定路由匹配后被挂载的组件
  • 需要直接传递组件的类
  • 通过 component 构建的组件,会自动创建组件并自动传递参数
    • match :匹配的信息
      • isExact :检查路径是否完全匹配
      • params :请求的参数
    • location :地址信息
    • history :历史记录信息,控制页面跳转
      • push() :跳转页面
      • replace() :替换页面

2./student/:id 会匹配到 /student/xxx

3.render

  • 也可以用于指定要挂载的组件
  • 需要一个回调函数作为参数,回调函数的返回值最终会被挂载
  • 不会自动传递 matchlocationhistory

4.children

  • 也可以用于指定要挂载的组件,两种用法
  • render 类似,传递回调函数【少用】
    • 该组件无论路径是否匹配都会挂载
  • 可以传递组件

5. src/App.js

import { Route } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Menu from "./components/Menu";
import Student from "./components/Student";

function App() {
  return (
    <div>
      App组件
      <Menu />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      {/* <Route path="/student/:id" component={Student} /> */}
      {/* <Route
        path="/student/:id"
        render={(routeProps) => {
          console.log(routeProps);
          return <Student {...routeProps} />;
        }}
      /> */}
      {/* <Route
        path="/student/:id"
        children={(routeProps) => {
          console.log(routeProps);
          return <Student {...routeProps} />;
        }}
      /> */}
      {/* <Route path="/student/:id" children={<Student />} /> */}
      <Route path="/student/:id">
        <Student />
      </Route>
    </div>
  );
}

export default App;

6. src/components/About.jsx

import React from "react";

const About = (props) => {
  console.log(props);

  const clickHandler = () => {
    // props.history.push({
    //   pathname: "/student/2",
    // });

    props.history.replace({
      pathname: "/student/2",
    });
  };

  return (
    <div>
      <button onClick={clickHandler}>点我一下</button>
      <h2>关于我们,其实是师徒四人</h2>
      <ul>
        <li>唐三藏</li>
        <li>孙悟空</li>
        <li>猪八戒</li>
        <li>沙和尚</li>
      </ul>
    </div>
  );
};

export default About;

7. src/components/Menu.jsx

import React from "react";
import { Link } from "react-router-dom";
import "./Menu.module.css";

const Menu = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">主页</Link>
        </li>
        <li>
          <Link to="/about">关于</Link>
        </li>
        <li>
          <Link to="/student/1">学生</Link>
        </li>
      </ul>
    </div>
  );
};

export default Menu;

8. src/components/Student.jsx

import React from "react";
import { useHistory, useLocation, useParams, useRouteMatch } from "react-router-dom";

const STU_DATA = [
  {
    id: 1,
    name: "aaa",
  },
  {
    id: 2,
    name: "bbb",
  },
  {
    id: 3,
    name: "ccc",
  },
  {
    id: 4,
    name: "ddd",
  },
];

const Student = (props) => {
  // console.log(props.match);
  // const stu = STU_DATA.find((item) => item.id === +props.match.params.id);
  const stu = STU_DATA.find((item) => item.id === 1);

  /*
    除了可以通过props获取三个对象外,还可以通过钩子函数获取
  */
  const match = useRouteMatch();
  const location = useLocation();
  const history = useHistory();
  const params = useParams();
  console.log(match, location, history, params);

  return (
    <div>
      {stu.id} --- {stu.name}
    </div>
  );
};

export default Student;

(五)路由嵌套

1. src/App.js

import { Route } from "react-router-dom";
import About from "./components/About";
import Hello from "./components/Hello";
import Home from "./components/Home";
import Menu from "./components/Menu";

function App() {
  return (
    <div>
      App组件
      <Menu />
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      {/* 路由嵌套,也可以定义于About组件中 */}
      {/* <Route path="/about">
        <About />
        <Route path="/about/hello">
          <Hello />
        </Route>
      </Route> */}
    </div>
  );
}

export default App;

2. src/components/About.jsx

import React from "react";
import { Route, useRouteMatch } from "react-router-dom";
import Hello from "./Hello";

const About = (props) => {
  console.log(props);

  const { path } = useRouteMatch();

  const clickHandler = () => {
    // props.history.push({
    //   pathname: "/student/2",
    // });

    props.history.replace({
      pathname: "/student/2",
    });
  };

  return (
    <div>
      <button onClick={clickHandler}>点我一下</button>
      <h2>关于我们,其实是师徒四人</h2>
      <ul>
        <li>唐三藏</li>
        <li>孙悟空</li>
        <li>猪八戒</li>
        <li>沙和尚</li>
      </ul>

      <Route path={`${path}/hello`}>
        <Hello />
      </Route>
    </div>
  );
};

export default About;

3. src/components/Hello.jsx

import React from "react";

const Hello = () => {
  return <div>Hello</div>;
};

export default Hello;

(六)Switch 组件

1. src/App.js

  • 可以将 Route 统一放到一个 Switch
  • 一个 Switch 中只会有一个路由显示
import { Route, Switch } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Menu from "./components/Menu";

function App() {
  return (
    <div>
      App组件
      <Menu />
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about">
          <About />
        </Route>
        <Route path="*">
          <div>路径错误</div>
        </Route>
      </Switch>
    </div>
  );
}

export default App;

(七)Prompt 组件

1. src/App.js

import { Route, Switch } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Menu from "./components/Menu";
import MyForm from "./components/MyForm";

function App() {
  return (
    <div>
      App组件
      <Menu />
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about">
          <About />
        </Route>
        <Route path="/form">
          <MyForm />
        </Route>
        <Route path="*">
          <div>路径错误</div>
        </Route>
      </Switch>
    </div>
  );
}

export default App;

2. src/components/MyForm.jsx

import React, { useState } from "react";
import { Prompt } from "react-router-dom";

const MyForm = () => {
  const [isPrompt, setIsPrompt] = useState(false);

  return (
    <div>
      <Prompt when={isPrompt} message={"将要离开页面!确认吗?"} />
      <h2>表单</h2>
      <input type="text" onChange={(e) => setIsPrompt(e.target.value.trim().length !== 0)} />
    </div>
  );
};

export default MyForm;

(八)Redirect 组件

1. src/App.js

import { useState } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Menu from "./components/Menu";
import MyForm from "./components/MyForm";
import Login from "./components/Login";

function App() {
  const [isLogin, setIsLogin] = useState(false);

  return (
    <div>
      App组件
      <Menu />
      <Switch>
        <Route exact path="/" component={Home} />

        <Route path="/about">
          <About />
        </Route>

        <Route path={"/login"}>
          <Login />
        </Route>
        <Route path="/form">
          {/* <MyForm /> */}
          {/* {isLogin ? <MyForm /> : <div>请登录后再操作!</div>} */}
          {isLogin ? <MyForm /> : <Redirect to={"/login"} />}
        </Route>

        <Redirect from={"/abc"} to={"/form"} />

        <Route path="*">
          <div>路径错误</div>
        </Route>
      </Switch>
    </div>
  );
}

export default App;

2. src/components/About.jsx

  • 用于跳转页面,替换当前 About 组件
  • push 属性改为跳转不替换
  • 也可以定义在 Switch 组件中
import React from "react";
import { Redirect, Route, useRouteMatch } from "react-router-dom";
import Hello from "./Hello";

const About = (props) => {
  console.log(props);

  const { path } = useRouteMatch();

  const clickHandler = () => {
    // props.history.push({
    //   pathname: "/student/2",
    // });

    props.history.replace({
      pathname: "/student/2",
    });
  };

  return (
    <div>
      {/* <Redirect push to={"/form"} /> */}

      <button onClick={clickHandler}>点我一下</button>
      <h2>关于我们,其实是师徒四人</h2>
      <ul>
        <li>唐三藏</li>
        <li>孙悟空</li>
        <li>猪八戒</li>
        <li>沙和尚</li>
      </ul>

      <Route path={`${path}/hello`}>
        <Hello />
      </Route>
    </div>
  );
};

export default About;

3. src/components/Login.jsx

import React from "react";

const Login = () => {
  return <div>登录页面</div>;
};

export default Login;
上次编辑于: