React Router 路由基础用法讲解(下)
8. 编辑&更新数据
嗯,没什么好说的,主要是对action的应用,提两个注意点
action函数的返回可用于重定向,用react自带的redirect方法,执行完自动跳转到原地址后+拼接地址。
export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}
Form表单的提交信息会被送到原地址+action内容拼接成的新路由里的action函数
<Form action="edit">
<button type="submit">Edit</button>
</Form>
9. NavLink 导航链接
当前 URL 匹配到这个 to 路径时,它就会自动获得一个默认的 active 类,获得相关样式,而Link标签不会
<NavLink to={`contacts/${contact.id}`}>
{contact.name}
</NavLink>
灵活用法:className 接收函数
<NavLink
to={`contacts/${contact.id}`}
className={({ isActive, isPending }) =>
isActive
? "active"
: isPending
? "pending"
: ""
}
>
{/* other code */}
</NavLink>
与之前不显式加这个类目最大的区别就是有一个加载中pending(待处理),如果你想在加载中加小圈圈样式的话就这样写 (这个箭头函数是三元运算符的嵌套使用,我一开始看了很久也没看出来)
10. useNavigation | 导航对象
作用:在整个应用级别追踪导航状态,让你知道页面是否正在跳转、加载数据、提交表单。
navigation.state 的三种取值
"idle"
没有正在进行的导航
应用处于静止状态
"loading"
正在跳转到新页面(新路由的
loader在跑)常用于显示全局的 页面加载中 指示
"submitting"
表单提交时(
<Form method="post">或fetcher.submit)常用于显示 表单提交中 状态
基本用法:
import { useNavigation } from "react-router-dom";
export default function App() {
const navigation = useNavigation();
return (
<div>
{navigation.state === "loading" && <p>Loading...</p>}
{navigation.state === "submitting" && <p>Submitting...</p>}
</div>
);
}
11. 上下文错误处理
我在destroy.jsx 文件里的action 加上这么一句throw new Error("oh dang!");
import { redirect } from "react-router-dom";
import { deleteContact } from "../contacts";
export async function action({ params }) {
throw new Error("oh dang!");
await deleteContact(params.contactId);
return redirect("/");
}
这样,当我每次点击删除按钮,进入destroy的路由,会直接抛出错误,进入错误页面而不会返回根路由,除非刷新,用户实际上无法执行任何作来从此屏幕中恢复。
然后我在配置路由里加上 errorElement: <div>Oops! There was an error.</div>,
// 1.创建路由对象
const router = createBrowserRouter([
{
path : '/',
element :<Root></Root>,
errorElement : <ErrorPage/>,
loader : rootLoader,
action: rootAction,
children : [
/* other routes */
{
path: "contacts/:contactId/destroy",
action: destroyAction,
errorElement: <div>Oops! There was an error.</div>,
}
]
}
])
这时错误信息就变成子组件,只渲染在子组件的那部分
补充细节:errorElement 的层级
错误会被最近的带有
errorElement的路由捕获如果当前路由没有配置
errorElement,错误会冒泡到父路由的errorElement(所以一开始进入的错误页面是根路由下的)如果一路冒泡都没人接,最终会由根路由的
errorElement兜底
12. 索引路由
其实可以理解为在你还没有选择任何子路由时进入的默认子路由
作用:用于给父路由的 <Outlet /> 提供默认内容
// existing code
import Index from "./routes/index";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{ index: true, element: <Index /> },
/* existing routes */
],
},
]);
13.取消按钮
使用 useNavigate 添加取消按钮点击处理程序,实现回退操作
import { useNavigate, } from "react-router-dom";
const navigate = useNavigate();
//使用前在合适位置加入这两行
<button
type="button"
onClick={() => {
navigate(-1);
}}
>
Cancel
</button>
14. 将URL同步到表单
<form role="search"> <input name="q" type="search" placeholder="Search" /> </form>输入
ryan回车:浏览器会自动把输入序列化到 URL 里(没加
method="post",HTML 的<form>默认就是 GET)
http://127.0.0.1:5173/?q=ryan
需求 : 希望url和搜索框保持同步
两步,1.获取url上q值 2.渲染到输入框 (感觉这波操作好像受控绑定)
export async function loader({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return { contacts ,q};
}
//这里直接进入组件内部搜索框部分
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
defaultValue={q}
/>
<div id="search-spinner" aria-hidden hidden={true} />
<div className="sr-only" aria-live="polite"></div>
</Form>
defaultValue={q},看起来很完美,你觉得这样好了吗?
当我们点击回退,url上参数清空,但搜索框内容并没有清空,为啥呢?
defaultValue只在 第一次渲染时生效,回退后,React 并不会重新赋值给输入框
两种解决方案
- 使用
<Form>的key强制重置
简单粗暴,把原来的组件销毁,创建新组件,这下总是每次都要重新渲染了吧
<Form key={q} method="get">
<input name="q" defaultValue={q} />
</Form>
- 用useEffect更新状态
useEffect(() => {
document.getElementById("q").value = q;
}, [q]);
15. 搜索框输入内容实时过滤
需求:使用useSubmit,每次击键时进行过滤,而不是在显式提交表单时进行过滤
其实就是每次触发搜索框内容改变事件就自动提交
useSubmit()返回一个函数submit
submit(formOrData, options):
formOrData可以是<form>元素,也可以是FormData或普通对象options可以指定:action(提交到哪个路由,如果不写默认就是当前路由)method(GET/POST 等)
//引入调用
import {
// existing code
useSubmit,
} from "react-router-dom";
const submit = useSubmit();
//。。。添加改变事件
<Form id="search-form" role="search">
<input
{/* existing code */}
onChange={(event) => {
submit(event.currentTarget.form);
}}
/>
{/* existing code */}
</Form>
-
16. 添加搜索状态提示
navigation.location表示当前即将导航到的 URL,如果没有导航中,它可能是null
-
const searching = navigation.location && new URLSearchParams(navigation.location.search).has("q");
- 当
navigation.location无值 → 当前没有正在进行的导航,也就是页面已经处于稳定状态,所有数据加载完毕,直接返回- 当
navigation.location有值 → 页面正在导航- 我们检查 URL 是否包含我们关心的参数(比如
q) → 用这个判断“是否处于搜索加载中”- 不需要等目标路由其他组件的数据全部加载完成,只关注
q对应的数据
添加类名,改变搜索框前面图标添加状态,一般是转圈的圆圈
className={searching ? "loading" : ""}
17. 替换历史记录history
前面我们实现了一个搜索框与url同步的功能,每次改搜索条件,就在历史记录里留一堆 /search?q=... 的记录,但是这样的话回退好多次才能回到最初搜索框为空的状态
怎么样才能实现点击“后退按钮”能回到最初状态呢?
onChange={(event) => {
const isFirstSearch = q==null;
submit(event.currentTarget.form,{
replace:!isFirstSearch,
})
}}
replace是控制浏览器历史记录(history stack)的:
replace: false(默认)
提交后会 新增一个历史记录条目。
用户按浏览器的 “后退” 按钮会回到之前的状态。
replace: true
提交后会 替换当前的历史记录条目。
用户按“后退”不会回到前一次输入状态,而是直接跳到更早的状态。
第一次搜索会新建历史记录,之后的搜索则替换掉当前的历史记录,避免“后退”翻太多次。
18. usefetch
用于不用跳转的突变,转到原来的路由,调用原路由action更新数据
记得把Form改成fetch.Form

const favorite = fetcher.formData?fetcher.formData.get('favorite') === 'true' : contact.favorite
fetcher捕获器可以获取表单提交数据,从而在action执行前就修改favortate状态,看起来操作更流畅
19. jsx配置路由
直接贴图吧,和之前写对象的模式有个区别就是子路由写在标签内


参考官方文档 :教程 v6.28.0 |React 路由器
参考视频:https://www.bilibili.com/video/BV1je411i7MH
就这么多啦!完结
更多推荐
所有评论(0)