实战
路由
- 路由设计,网址和页面的关系,就是从业务上分析需要哪些页面哪些页面内容可以抽离,业务流程要有入有出
- 增加页面和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>
)}
<span>答卷:{answerCount}</span>
<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目录下的文件比较特别,需要跳转的时候传参,进行完上面的设置后可以在路径后面加入参数测试是否成功,如图所示