上集精彩
回顾上集:
- 好了,俺要开始学习React了 (基础一) - 掘金 (juejin.cn);
- 好了,俺要开始学习React了(基础二) - 掘金 (juejin.cn);
- 好啦,俺要开始学React啦(基础三)真是一场酣畅淋漓的写作时刻 - 掘金 (juejin.cn);
- React,你是对Redux情有独钟,还是对Zustand非你不嫁? - 掘金 (juejin.cn);
- React,来个小案例总结一下 - 掘金 (juejin.cn);
上集俺讲了个案例。
本集继续
😁😁😁,先别嫌俺烦,俺先叨叨(确实烦😅)
ReactRouter
使用React这些工具所编写的项目通常都是单页应用(SPA),上述的案例都可以叫单页应用。单页应用中,整个应用中只含有一个页面,React会根据不同的状态在应用中显示出不同的组件。要想实现多页面应用,需要引入一个新的工具React Router,React Router为我们提供一种被称为客户端路由的东西,通过客户端路由可以将URL地址和React组件进行映射,当URL地址发生变化时,它会根据设置自动的切换到指定组件。并且这种切换完全不依赖于服务器。换句话说,在用户看来浏览器的地址栏确实发生了变化,但是这一变化并不由服务器处理,而是通过客户端路由进行切换。
官网:Home v6.21.3 | React Router
React Router6新出来的,虽然与5有很大的区别和新特性,5和6都认识下吧,相当于一个过度吧。
css
npm install react-router-dom@5
在 index.js
中引入 BrowserRouter
并包裹App组件(或起个别名 BrowserRouter as Router
)
然后就可以在项目中使用React Router了。
Route 组件
- React Router 的url地址可以和组件进行映射(用户访问某个地址时,与其对应的组件会自动挂载)
不知道看到这你会不会这样想,不是说用户访问某个地址时,与其对应的组件会自动挂载,那俺访问了 /about
后,/
路径的Home组件还会显示呢。
当 Route 的路径被访问,其对应组件就会对应挂载,其实这句话没毛病,你想的那是叫严格匹配。注意哈,默认情况下 Route 不能严格匹配,只要url地址地址的头部与path一致,组件就会挂载。要想做到你想的那样的严格匹配的话,添加个 exact
js
{ /* exact 路径是否严格匹配 */ }
<Route exact path="/" component={Home} />
component
用来指定路由匹配后被挂载的组件,需要直接传递组件的类,通过component构建的组件,它会自动创建组件并传递参数(直接在组件内传递props即可看到)
js
// components/About/index.js
const About = (props) => {
console.log(props);
-
match -> 匹配的信息
-
isExact 检查路径是否完全匹配
-
params 请求的参数
About组件的path修改下
path="/about/:id"
,然后控制台打印console.log(props.match.params)
:改进下:
-
-
location -> 地址信息
-
history -> 控制页面的跳转
- push() 跳转页面(可回退到之前页)
- replace() 替换页面(不可回退到之前页)
- 除了
component
挂载组件,还有另一个方式render
render
也可以用来指定被挂载的组件,它需要一个回调函数作为参数,回调函数的返回值会最终被挂载,它不会向 component
一样自动传递三个属性,要想得到这三个属性需要这样:
组件通过props就可接收到这三个属性
- 除了
render
挂载组件,还有另一个方式children
children
也可以用来指定被挂载的组件,用法有两种:
- 和render类似,传递回调函数(直接把上图的render改为children就行),当children设置一个回调函数时,该组件无论路径是否匹配都会挂载
- 可以直接传递组件
<Route exact path="/about/:id" children={<About />} />
(或者这样写<Route exact path="/about/:id"><About /></Route>
)
除了通过props获取三个对象外,还可以通过钩子函数来获取:
路由的嵌套
同样也可以将嵌套路由放在About组件里,只是层级不一样了,使用还是不变的。
那如果about的嵌套路由有很多的话,想把 /about/xxx/xxx~~~
中的about动态获取(即父组件地址)
Prompt组件
可以将路由Route统一放在一个Switch中,一个Switch中只会有一个路由显示
当你使用到表单时,如果你没有写完表单内容跳转到其他页面了,再返回的话,表单的内容就会重置,这样会很麻烦,使用Prompt组件,会在你离开页面之前弹窗提示
改进下:(就是防止误操作导致数据丢失)
Redirect组件 重定向
在About组件中使用Redirect组件重定向到form中
js
// components/About/index.js
import { Route, useRouteMatch, Redirect } from "react-router-dom";
{/* to 重定向到xxx
from 从xxx 如 from={"/abc"} to={"/form"} 输入地址abc到form
*/}
<Redirect from={"/abc"} to={"/form"} />
Link 和 NavLink
然后在App.js中导入并使用,当你点击两个链接时,它是不会向服务器重新发送请求的。但当你把 Link
换成 a
的话,那就不行了,a
会重新向服务器发送请求,不能这样干。
NavLink
的功能会比 Link 更加丰富:它主要会涉及到样式
看,不管你咋跳转,它都不会发请求,当你跳转到对应页面时,它就会显示对应路径的链接粉色加下划线。
两种 Router
-
HashRouter 会通过url地址中的hash值来对地址进行匹配(就是根路径后多个
#
号) -
BrowserRouter 直接通过url地址进行组件的跳转,(不带
#
号)当通过点击NavLink构建的链接进行跳转时,跳转并没经过服务器,这没问题
但当刷新页面时或通过普通链接跳转时,会向服务器发送请求加载数据,这时的请求并没有经过react router,所以返回404
-
解决方案:
-
使用HashRouter 重新构建并粘贴。服务器是不会处理带hash值的地址,所以使用HashRouter后请求将有react route处理
-
修改服务器的配置,将所有请求都转发到index.html。找到nginx下的conf目录,打开nginx.conf,找到对应字段并进行如下修改:
try_files $uri /index.html;
意思就是不管你访问啥url地址,最后都给俺转发到index.html里重新加载一下服务器
.\nginx.exe -s reload
。可能会打印错误,俺没用过nginx就用了比较死板的法子,打开任务管理器,停止nginx服务。然后再点击nginx.exe文件重启服务就行了。
-
nginx服务器:nginx news
下载后,解压,点击 nginx.exe
即可启动服务器,访问 127.0.0.1
项目打包后,将其目录下所有文件粘贴到nginx下的html目录里。
要想停止服务的话,在nginx安装目录下打开终端:
ReactRouter6
之前讲的ReactRouter都是5的知识点,ReactRouter6相对于5来讲改动还是很大的,把很多东西都化繁为简了,体积相对于之前的版本也小了很多比如 Switch,history,match等在6中都没有了,被其他东西替代了
cmd
npm i react-router-dom
- 可以通过useParams() 来获取参数:
- useLocation() 还在:获取当前地址的信息
js
const location = useLocation();
console.log(location);
- useMatch() 检查当前url是否匹配某个路由
js
const match = useMatch("/about");
console.log(match);
如果路由匹配,则返回个对象,不匹配返回null
- useNavigate() 获取一个用于页面跳转的函数:
js
const navigate = useNavigate();
const clickHandler = () => {
// navigate("/student/1"); // 使用push 会产生历史记录 可回退
navigate("/student/1", { replace: true }); // 使用replace 不会产生历史记录 不可回退
};
v6中的嵌套路由
- 通过子路由来对Good进行映射 /about/good:
js
// App.js
<Route path="about/*" element={<About />} />
// components/About/index.js
<Routes>
<Route path="good" element={<Good />} />
</Routes>
v6中是默认严格匹配的,在后面加上 ***** 后就可不按照严格匹配来了。
注意了: 你看这,App.js中有路由,About组件中又有路由,要是每个组件都设置路由的话,那是不是非常不便于管理和维护。想要的是所有的路由可以统一写在一个位置,你可以这样:
js
// App.js
<Route path="about" element={<About />}>
{/* 嵌套路由 */}
<Route path="good" element={<Good />} />
</Route>
嵌套路由也用了,但当你访问 /about/good
时并没有出现Good组件的页面,此时你还需要一步,在About组件中使用Outlet组件,它用来表示嵌套路由中的组件,当嵌套路由中的路径匹配成功,Outlet则表示嵌套路由中的组件,反之啥也不显示
js
import { Outlet } from "react-router-dom";
// components/About/index.js
const About = () => {
return (
<>
<h1>Welcome to the About Page!</h1>
<ul>
<li>React</li>
</ul>
<Outlet />
</>
);
};
Navigate组件 路由跳转
Navigate 组件用来跳转到指定的位置,默认使用push跳转,回退不了,要想回退的话,加个 replace
js
<Navigate to={"/student/3"} replace />
NavLink组件
改了一种方式去设置样式:
js
<NavLink
style={({ isActive }) => {
return isActive ? { color: "black" } : null;
}}
to="/student/2"
>
Student
</NavLink>
ReactRouter 工程化
抽离路由模块
工程化即模块化,router示例写在index.js中的话就不符合了。能不能创建个单独的模块,将router相关的放到其中,可以抽离路由模块。
- 在src下创建
pages
: 用于放页面的;并在src下兴建router
,专门用来放路由相关的
接着再导入到index.js中并使用
js
import router from "./router";
路由导航
- 前面讲的Link、NavLink、useNavigate() 都可用于路由跳转,要说区别的话
- 声明式导航:
Link、NavLink
(会被转义为a链接) - 编程式导航:
useNavigate()
通过该钩子函数使用导航
- 通过
useSearchParams()
也可以实现路由传参
路由嵌套(子路由)
- 通过
children
配置子路由:
- 在Layout组件中添加二级路由出口:
- 如何在显示Layout组件的同时,显示Home组件,默认二级路由就是将
path: "/home"
换成index: true
- 当路由找不到时,用404路由兜底
js
{
path: "*",
element: <NotFound />
}
- 要想将路由模式换成hash模式直接将
createBrowserRouter
换为createHashRouter
样式相关
补充: 好像之前也没有说到样式相关的内容,那就在这里补充一下吧。希望不会太晚😅
内联样式和样式表
- 通过在标签内直接使用
style={{}}
:
- 当内联样式多的话,可以把它提出来:
- 动态设置样式:需要借助
useState
内联样式肯定不适合大项目。
- 样式表的话:
它也不适合,有缺点,import "./index.css";
这样如果是在App.js中引入那就是全局引入,如果再在 src/index.js
下引入的话那就会形成样式冲突(类名重复的话),不适合大范围的使用,这种方式适合引入一些通用样式。
CSS Module
模块化CSS,适合大范围的使用(一种自动写类名的方式)。
使用步骤:
- 创建css文件时,以
xxx.module.css
命名,说明你创建的时一个css模块- 在组件中引入css,
import classes from "./index.module.css";
- 通过
classes
来设置类,className={classes.title}
。CSS模块可以动态的设置唯一的class值(类名都是自动生成的,就是确保唯一性)
Fragment
拓展: 在上图中,你可能会看到这个 <></>
,不知道你会不会对这玩意产生好奇,也不管你了,爱咋咋地,俺先简单说下这是个什么东西。
在react中,组件都必须有个跟容器,然后在跟标签里编写子标签。好,那俺在每个组件写个div父容器呗,但绝大多数都用不上这个div呀(就是个多鱼的结构)。但俺就是不想加那个div,俺就感觉难受。好,那俺有三种法子来解决:
- 方法一:自定义个组件:组件直接引入父容器Out并使用就行了
js
// src/Out.js
const Out = (porps) => {
// 只会返回子元素
return porps.children;
};
export default Out;
- 方法二:使用 Fragment:它是由React提供的专门作为父容器的组件,它只会讲里面的子元素直接返回,不会创建任何多鱼的元素
js
import { Fragment } from "react";
const Good = () => {
return (
<Fragment>
<h1>Welcome to the Good Page!</h1>
</Fragment>
);
};
- 方法三:使用空标签
<></>
,它是react提供的语法糖
下集精彩
好了,到这,React的所有基础知识点(有点绝对了😀)。该说的都说了,能保证在后期的项目中够用就行了,不行的话再查,再学呗。正所谓:我不入地狱谁入地狱 ,哎!不对,应该是:活到老学到老。不好意思好😁
下篇俺会讲到哪些知识点呢: 俺将正式迈入工程化的大门。虽然本集已讲了点点了ReactRouter的工程化,算是半只脚进门吧。
- React 工程化;
只要懂得这篇的知识点就很棒了。