react-问卷星项目(5)

实战

路由

  • 路由设计,网址和页面的关系,就是从业务上分析需要哪些页面哪些页面内容可以抽离,业务流程要有入有出
  • 增加页面和Layout模版,模版就是抽离页面公共部分,比如都有顶部或者左侧导航,直接上代码,就是组件复用的思想
  • 使用React-router增加路由配置(目前路由模块只有这一个工具)

React-router

路由工具

其中Outlet和vue中的slot插槽相似,通过下载路由并导入获得

下载React-router

npm install react-router-dom --save

这一篇实战性比较强,直接上代码,相关部分在代码中都有注释

项目的目录结构如下,components是存放组件,Layouts存放布局,布局就是页面抽离出的公共部分,比如几个几个页面都有顶部和底部,这两个就可以抽出来成布局,pages存放页面,在React中都是组件,但是在业务上我们称之为页面,结构就是右边这个图所示,有点模糊凑合着看,从老师的视频里截出来的

全部先新建出来后按照下面的格式先布局就可以。

复制代码
// 星标问卷页面
import React, { FC } from "react";
const Star: FC = () => {
  return <p>Star</p>;
};
export default Star;

接下来一个个页面开始细化,基本思路就是先list文件挂载到app中,然后添加问卷列表卡片组件Question,再然后完善布局,其中布局有些注意点就是,一部分是固定的,但是还有一部分是可以切换的,可以切换的这一块需要用的路由的一个类,下面碰到的时候会注释。

ps:新项目记得把sass之类的下载好,忘记指令的可以看这篇,反正什么报错下载什么就可以

List.tsx

TypeScript 复制代码
import React, { FC, useState } from "react";
import { useSearchParams } from "react-router-dom";
import QuestionCard from "../../components/QuestionCard";
import styled from "./List.module.scss";

const rawQuestionList = [
  {
    _id: "q1",
    title: "问卷1",
    isPublished: true,
    isStar: false,
    answerCount: 5,
    createAt: "3月10日 13:23",
  },
  {
    _id: "q2",
    title: "问卷2",
    isPublished: false,
    isStar: true,
    answerCount: 15,
    createAt: "3月22日 13:23",
  },
  {
    _id: "q3",
    title: "问卷3",
    isPublished: true,
    isStar: true,
    answerCount: 100,
    createAt: "4月10日 13:23",
  },
  {
    _id: "q4",
    title: "问卷4",
    isPublished: false,
    isStar: false,
    answerCount: 98,
    createAt: "3月23日 13:23",
  },
];

const List: FC = () => {
  const [searchParams] = useSearchParams();
  console.log("keyword", searchParams.get("keyword"));

  const [questionList, setQuestionList] = useState(rawQuestionList);
  return (
    <>
      <div className={styled.header}>
        <div className={styled.left}>
          <h3>我的问卷</h3>
        </div>
        <div className={styled.right}>搜索</div>
      </div>
      <div className={styled.content}>
        {questionList.map((q) => {
          const { _id } = q;
          return <QuestionCard key={_id} {...q} />;
        })}
      </div>
      <div className={styled.footer}>footer</div>
    </>
  );
};

export default List;

List.module.scss

css 复制代码
.header{
  display: flex;
  .left{
    flex: 1;
  }
  .right{
    flex: 1;
    text-align: right;
  }
}

.content{
  margin-bottom: 20px;
}

.footer{
  text-align: center;
}

body{
  background-color: #f1f1f1;
}

QuestionCard.tsx

TypeScript 复制代码
import React, { FC, useEffect } from "react";
// import "./QuestionCard.css";
import styled from "./QuestionCard.module.scss";
import classnames from "classnames";
type PropsType = {
  _id: string;
  title: string;
  isPublished: boolean;
  isStar: boolean;
  answerCount: number;
  createAt: string;
  // 问号是可写可不写,跟flutter语法相似
  deletQuestion?: (id: string) => void;
  pubQuestion?: (id: string) => void;
};

const QuestionCard: FC<PropsType> = (props: PropsType) => {
  const { _id, title, createAt, answerCount, isPublished } = props;

  return (
    <div className={styled.container}>
      <div className={styled.title}>
        <div className={styled.left}>
          <a href="#">{title}</a>
        </div>
        <div className={styled.right}>
          {isPublished ? (
            <span style={{ color: "green" }}>已发布</span>
          ) : (
            <span>未发布</span>
          )}
          &nbsp;
          <span>答卷:{answerCount}</span>
          &nbsp;
          <span>{createAt}</span>
        </div>
      </div>
      <div className={styled["button-container"]}>
        <div className={styled.left}>
          <button>编辑问卷</button>
          <button>数据统计</button>
        </div>
        <div className={styled.right}>
          <button>标星</button>
          <button>复制</button>
          <button>删除</button>
        </div>
      </div>
    </div>
  );
};

export default QuestionCard;

QuestionCard.module.scss

TypeScript 复制代码
.container{
  margin-bottom: 20px;
  padding: 12px;
  border-radius: 3px;
  background-color: white;

  &:hover{
    box-shadow: 0 4px 10px lightgray;
  }
}

.title{
  display: flex;
  .left{
    flex: 1;
  }
  .right{
    flex: 1;
    text-align: right;
  }
}

.button-container{
  display: flex;
  .left{
    flex: 1;
  }
  .right{
    flex: 1;
    text-align: right;
    button{
      color: #999;
    }
  }
}

下面开始就是布局文件,其中用到的Outlet和vue中的插槽比较相似,作用就是占位,可以实现切换组件的效果

MainLayout.tsx

TypeScript 复制代码
import React, { FC } from "react";
import { Outlet } from "react-router-dom";
const MainLayout: FC = () => {
  return (
    <div>
      <div>MainLayout header</div>
      <div>
        <Outlet />
      </div>
      <div>MainLayout footer</div>
    </div>
  );
};
export default MainLayout;

ManagerLayout.tsx

TypeScript 复制代码
import React, { FC } from "react";
import { Outlet } from "react-router-dom";
import styled from "./MangerLayout.module.scss";
const MangerLayout: FC = () => {
  return (
    <div className={styled.container}>
      <div className={styled.left}>
        <p>MainLayout left</p>
        <button>创建问卷</button>
        <br />
        <a href="#">我的问卷</a>
        <br />
        <a href="#">星标问卷</a>
        <br />
        <a href="#">回收站</a>
        <br />
      </div>
      <div className={styled.right}>
        <Outlet />
      </div>
    </div>
  );
};
export default MangerLayout;

MangerLayout.module.scss

TypeScript 复制代码
.container{
  display: flex;
  padding: 24px 0;
  width: 1200px;
  margin: 0 auto; // 水平居中
  .left{
    width: 120px;
    background-color: aqua;
  }
  .right{
    flex: 1;
    margin-left: 60px;
    background-color: aquamarine;
  }

}

QuestionLayout.tsx

TypeScript 复制代码
import React, { FC } from "react";
import { Outlet } from "react-router-dom";

const QuestionLayout: FC = () => {
  return (
    <div>
      <div>QuestionLayout header</div>
      <div>
        <Outlet />
      </div>
      <div>QuestionLayout footer</div>
    </div>
  );
};
export default QuestionLayout;

接下来是路由器编辑

router/index.tsx

TypeScript 复制代码
// 路由配置
import React from "react";
import { createBrowserRouter } from "react-router-dom";
import MainLayout from "../Layouts/MainLayout";
import ManagerLayout from "../Layouts/ManagerLayout";
import QuestionLayout from "../Layouts/QuestionLayout";
import Home from "../pages/Home";
import Login from "../pages/Login";
import Register from "../pages/Register";
import NotFound from "../pages/NotFound";
import List from "../pages/manager/List";
import Trash from "../pages/manager/Trash";
import Star from "../pages/manager/Star";
import Edit from "../pages/manager/question/Edit";
import Static from "../pages/manager/question/Static";

// 数组表示可以创建多路径
const router = createBrowserRouter([
  {
    path: "/",
    // 访问根目录时element指向MainLayout
    element: <MainLayout />,
    children: [
      {
        path: "/",
        element: <Home />,
      },
      {
        path: "login",
        element: <Login />,
      },
      {
        path: "register",
        element: <Register />,
      },
      {
        path: "manager",
        element: <ManagerLayout />,
        children: [
          {
            path: "list",
            element: <List />,
          },
          {
            path: "star",
            element: <Star />,
          },
          {
            path: "trash",
            element: <Trash />,
          },
        ],
      },
      {
        // 以上页面都没有命中
        path: "*",
        element: <NotFound />,
      },
      {
        path: "question",
        element: <QuestionLayout />,
        children: [
          {
            path: "edit",
            element: <Edit />,
          },
          {
            path: "static",
            element: <Static />,
          },
        ],
      },
    ],
  },
  {
    path: "question",
    element: <QuestionLayout />,
    children: [
      {
        path: "edit/:id",
        element: <Edit />,
      },
      {
        path: "static/:id",
        element: <Static />,
      },
    ],
  },
]);
export default router;

最后挂载到App上就可以

App.tsx

TypeScript 复制代码
import { RouterProvider } from "react-router-dom";
import router from "./router";

function App() {
  return <RouterProvider router={router}></RouterProvider>;
}

export default App;

可以在路径上添加相应后缀尝试是否能正确切换,接下来就是传值和接收值的测试,包括使用路由进行页面跳转

Home.tsx

TypeScript 复制代码
// 首页
import React, { FC } from "react";
import { useNavigate, Link } from "react-router-dom";

const Home: FC = () => {
  const nav = useNavigate();

  function clickHandler() {
    nav({
      pathname: "/login", // 路径
      search: "b=21", // 路径附加参数,类似get
    });
  }

  return (
    <div>
      <p>Home</p>
      <div>
        <button onClick={clickHandler}>登录</button>
        <Link to="/register">注册</Link>
      </div>
    </div>
  );
};
export default Home;

Login.tsx

TypeScript 复制代码
// 登陆页面
import React, { FC } from "react";
import { useNavigate } from "react-router-dom";

const Login: FC = () => {
  const nav = useNavigate();

  return (
    <div>
      <p>Login</p>
      <div>
        {/* -1就是返回上一个 */}
        <button onClick={() => nav(-1)}>返回</button>
      </div>
    </div>
  );
};
export default Login;

Edit/index.tsx

TypeScript 复制代码
// 编辑页面,页面比较复杂所以不放在单个页面,而是文件夹中
import React, { FC } from "react";
import { useParams } from "react-router-dom";

const Edit: FC = () => {
  // 默认值,不传入就是空字符串
  const { id = "" } = useParams();
  return <p>Edit{id}</p>;
};
export default Edit;

question目录下的文件比较特别,需要跳转的时候传参,进行完上面的设置后可以在路径后面加入参数测试是否成功,如图所示

相关推荐
早點睡3904 小时前
高级进阶 React Native 鸿蒙跨平台开发:react-native-device-info 设备信息获取
react native·react.js·harmonyos
恋猫de小郭5 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅13 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment13 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端