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

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

.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

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

.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

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

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

.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

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

// 路由配置
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

import { RouterProvider } from "react-router-dom";
import router from "./router";

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

export default App;

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

Home.tsx

// 首页
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

// 登陆页面
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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值