利用 typescript 寫 react-router 5

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

react-router-dom

在 web 端使用,只須要導入這個包就能夠了,由於它從 react-router 中拿過來了不少東西。react

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

而後看看經常使用的有哪些功能web

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

路由組件,路由匹配時這個位置被渲染成相應的內容。react-router

  • path 須要匹配的路徑
  • component 匹配成功渲染的組件
  • exact 是否嚴格匹配
<Route path="/home" exact component={ Home } />

這個例子中,只有當路徑爲 /home 時纔會匹配。若沒有 exact 屬性,那麼當路徑爲 /home 時,//home這兩個路由組件都會被渲染。dom

嵌套路由

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 屬性。(目的是先渲染父組件,再匹配父組件內部定義的子路由組件)url

動態路由

和其餘路由插件同樣,使用冒號配置動態路由。插件

// 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 屬性。code

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 的規範,能夠在動態路由組件中,定義一個接口約定路由傳遞的參數。component

// 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);
相關文章
相關標籤/搜索