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

相关推荐
沙漏无语1 分钟前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport3 分钟前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg4 分钟前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww11 分钟前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_7482548812 分钟前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234521 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成1 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
噢,我明白了1 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__2 小时前
APIs-day2
javascript·css·css3