利用 typescript 写 react-router 5

不再提倡中心化路由!嵌套路由不再是 { props.children } 的用法了。每个路由都是一个 React 组件。

react-router-dom

在 web 端使用,只需要导入这个包就可以了,因为它从 react-router 中拿过来了很多东西。

// @types/react-router-dom/index.d.ts
export { …… } from 'react-router';

然后看看常用的有哪些功能

HashRouter / BrowerRouter

理解为路由容器,被包裹在里面的子组件就可以使用自己定义的路由组件了。

// index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App';

ReactDOM.render(
    <HashRouter>
        <App />
    </HashRouter>
    , document.getElementById('root')
);

Route

路由组件,路由匹配时这个位置被渲染成相应的内容。

  • path 需要匹配的路径
  • component 匹配成功渲染的组件
  • exact 是否严格匹配
<Route path="/home" exact component={ Home } />

这个例子中,只有当路径为 /home 时才会匹配。若没有 exact 属性,那么当路径为 /home 时,//home这两个路由组件都会被渲染。

嵌套路由

v4 以上版本不再支持 { props.children } 的方式进行嵌套路由,而是直接把子路由组件放在父组件中需要渲染的位置。

// App.tsx
<div>
    <Route path="/" component={ Dashboard } />
</div>

// Dashboard.tsx
<div>
    <Header />
    <Route path="/home" component={ Home } />
    <Route path="/other" component={ Other } />
</div>

这样的嵌套路由写法,需要保证父组件与子组件有相同的路由前缀(/),且父组件没有 exact 属性。(目的是先渲染父组件,再匹配父组件内部定义的子路由组件)

动态路由

和其他路由插件一样,使用冒号配置动态路由。

// Dashboard.tsx
<div>
    <Header />
    <Route path="/home" component={ Home } />
    <Route path="/other" exact component={ Other } />
    <Route path="/other/:id" component={ OtherDetail } />
</div>

/other/1 会匹配 /other/other/:id 这两个路由组件,根据实际情况对 /other 路由组件设置 exact 属性。

useParams 获取路由参数
// @types/react-router/index.d.ts
export function useParams<Params extends { [K in keyof Params]?: string } = {}>(): { [p in keyof Params]: string };

useParams() 方法返回的是一个对象,直接取属性 TS 会提示空对象中不存在这个属性。按照 TS 的规范,可以在动态路由组件中,定义一个接口约定路由传递的参数。

// OtherDetail.tsx
import React from 'react';
import { useParams } from 'react-router-dom';
interface RouteParams {
    id: string
}
export default () => {
    const params = useParams<RouteParams>();
    return (
        <div>
            动态路由:{ params.id }
        </div>
    )
}
props 获取路由参数

路由组件的 props 数据类型为 RouteComponentProps

// @types/react-router/index.d.ts
export interface RouteComponentProps<Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState> {
    history: H.History;
    location: H.Location<S>;
    match: match<Params>;
    staticContext?: C;
}

其中 match 属性会用的比较多

// @types/react-router/index.d.ts
export interface match<Params extends { [K in keyof Params]?: string } = {}> {
    params: Params;
    isExact: boolean;
    path: string;
    url: string;
}

在动态路由 /other/1 中,props.match.url 的值为 /other/1props.match.path 的值为 /other/:id。获取 props.match.params 中的属性仍然需要告诉 TS 有哪些属性。

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
interface RouteParams {
    id: string
}
export default (props: RouteComponentProps<RouteParams>) => {
    return (
        <div>
            动态路由:{ props.match.params.id }
        </div>
    )
}
useRouteMatch 获取路由匹配信息

上面说到可以使用 props 获取到与路由相关的信息,其中包括了 matchparams 等,可以使用 props.match 获取路由的匹配信息。也可以使用 useRouteMatch 方法。

// @types/react-router/index.d.ts
export function useRouteMatch<Params extends { [K in keyof Params]?: string } = {}>(
    path?: string | string[] | RouteProps,
): match<Params> | null;

注意 useRouteMatch() 的返回值可能是 null,不能简单的通过 match.* 的形式访问。

// Other.tsx
import React from 'react';
import { useRouteMatch } from 'react-router';
export default () => {
    const match = useRouteMatch();
    return (
        <div>路由路径:{ match && match.url }</div>
    )
}

useLocationuseHistory 的用法类似。

Switch

Switch 只会匹配子组件中的第一个路由组件。对于前面提到的,在不设置 exact 属性的前提下,/home 会同时匹配 //home 两个路由组件,使用 Switch 可以进行单一匹配,但与放置顺序也有关。

<Switch>
    <Route path="/home" component={ Home } />
    <Route path="/" component={ Dashboard } />
</Switch>

Link

封装了 <a> 标签的组件进行路由跳转。

<Link to="/home">to home</Link>

NavLink

Link 的用法类似,会默认给当前路由路径与 to 属性匹配的组件添加 active 类名。

<NavLink to="/home">to home</NavLink>
<NavLink exact to="/other">to other</NavLink>
<NavLink to="/other/1">to other/1</NavLink>

当点击 to other/1 链接时,to other 链接也会被添加上 active 类名,这与 Router 组件是类似的,所以对于这样的导航,通常需要添加 exact 属性。

Redirect

to 属性进行重定向,通常会用在 Switch 中,作为匹配失败的处理。

编程式路由

useHistory() 返回的 history 对象,调用 push 方法。

参数传递

params

// 路由组件
<Route path="/home/:id" component={ Home }/>

// Home.tsx
interface RouteParams {
    id: string
}
export default () => {
    const params = useParams<RouteParams>();
    return (
        <div>
            { params.id }
        </div>
    )
}

// Link 跳转
<Link to="/home/1">to home</Link>

// history 跳转
import { useHistory } from 'react-router-dom';
export default () => {
    const history = useHistory();
    const pushRouteParams = () => {
        history.push('/home/1')
    };
    return (
        <div>
            <Button onClick={ pushRouteParams }>to /home/1</Button>
        </div>
    );
};

state

// 路由组件
<Route path="/home" component={ Home }/>

// Home.tsx
import { useLocation } from 'react-router-dom';
export default () => {
    const location = useLocation();
    return (
        <div>
            { location.state && location.state.id }
        </div>
    )
}

// Link 跳转
<Link to={{ pathname: '/home', state: { id: 1 } }}>to home</Link>

// history 跳转
history.push({ pathname: '/home', state: { id: 1 } })

query

// @types/history
export interface Location<S = LocationState> {
    pathname: Pathname;
    search: Search;
    state: S;
    hash: Hash;
    key?: LocationKey;
}

location 对象没有 query 属性了,应该是不提供这个方法了吧……

push 和 replace

// Link 
<Link to="/home" />
<Link to="/home" replace/>

// history
history.push(...)
history.replace(...)

钩子函数

v4 之后不再提供 onEnteronUpdateonLeave等函数,而是在路由组件中分别对应 React 中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 等生命周期方法,这恰好就可以使用新特性 useEffect 进行替换了。

在路由组件内部使用 useEffect,配合 history.replace() 进行路由权限控制

const history = useHistory();
const state = true;
useEffect(() => {
    if (!state) {
        history.replace('/');
    }
});

也写成一个自定义 Hook,在多个路由组件中使用

function useUserRole(state: boolean) {
    const history = useHistory();
    useEffect(() => {
        if (!state) {
            history.replace('/');
        }
    });
}

useUserRole(false);
69 声望
6 粉丝
0 条评论
推荐阅读
单文件组件下的vue,可以擦出怎样的火花
与时俱进吧,看着 vue3 和 vite,虽然不会用,但还是心痒痒,然后就把原先基于 vue@2 的实现做了重构。不周之处,大家见谅!下面关于过期的内容,我就用删除线标记了。

leftstick64阅读 45.2k评论 18

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木149阅读 12.3k评论 10

正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青54阅读 7.8k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy46阅读 5.9k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木66阅读 6.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.3k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木43阅读 7.3k评论 6

69 声望
6 粉丝
宣传栏