這次的版本是 6.2.1
使用
相比較 5.x 版本, <Switch>元素升級為了<Routes>
簡單的 v6 例子:
function App(){
return <BrowserRouter>
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/users" element={<Users/>}/>
<Route path="/" element={<Home/>}/>
</Routes>
</BrowserRouter>
}
context
在 react-router 中, 他建立了兩個 context 供後續的使用, 當然這兩個 context 是在內部的, 並沒有 API 暴露出來
NavigationContext
/**
* 一個路由物件的基本構成
*/
export interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
index?: boolean;
path?: string;
}
// 常用的引數型別
export type Params<Key extends string = string> = {
readonly [key in Key]: string | undefined;
};
/**
* 一個 路由匹配 介面
*/
export interface RouteMatch<ParamKey extends string = string> {
/**
* 動態引數的名稱和值的URL
*/
params: Params<ParamKey>;
/**
* 路徑名
*/
pathname: string;
/**
* 之前匹配的路徑名
*/
pathnameBase: string;
/**
* 匹配到的路由物件
*/
route: RouteObject;
}
interface RouteContextObject {
outlet: React.ReactElement | null;
matches: RouteMatch[];
}
const RouteContext = React.createContext<RouteContextObject>({
outlet: null,
matches: []
});
LocationContext
import type {
Location,
Action as NavigationType
} from "history";
interface LocationContextObject {
location: Location; // 原生的 location 物件, window.location
/**
* enum Action 一個列舉, 他有三個引數, 代表路由三種動作
* Pop = "POP",
* Push = "PUSH",
* Replace = "REPLACE"
*/
navigationType: NavigationType;
}
const LocationContext = React.createContext<LocationContextObject>(null!);
MemoryRouter
在 react-router-dom
的原始碼解析中我們說到了 BrowserRouter
和 HashRouter
, 那麼這個 MemoryRouter
又是什麼呢
他是將 URL 的歷史記錄儲存在記憶體中的 <Router>(不讀取或寫入位址列)。在測試和非瀏覽器環境中很有用,例如 React Native。
他的原始碼和其他兩個 Router 最大的區別就是一個 createMemoryHistory
方法, 此方法也來自於 history
庫中
export function MemoryRouter({
basename,
children,
initialEntries,
initialIndex
}: MemoryRouterProps): React.ReactElement {
let historyRef = React.useRef<MemoryHistory>();
if (historyRef.current == null) {
historyRef.current = createMemoryHistory({ initialEntries, initialIndex });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
那我們現在來看一看這個方法, 這裡只講他與 createHashHistory
不同的地方:
export function createMemoryHistory(
options: MemoryHistoryOptions = {}
): MemoryHistory {
let { initialEntries = ['/'], initialIndex } = options; // 不同的初始值 initialEntries
let entries: Location[] = initialEntries.map((entry) => {
let location = readOnly<Location>({
pathname: '/',
search: '',
hash: '',
state: null,
key: createKey(), // 通過 random 生成唯一值
...(typeof entry === 'string' ? parsePath(entry) : entry)
}); // 這裡的 location 屬於是直接建立, HashHistory 中是使用的 window.location
// readOnly方法 可以看做 (obj)=>obj, 並沒有太大作用
return location;
});
function push(to: To, state?: any) {
let nextAction = Action.Push;
let nextLocation = getNextLocation(to, state);
function retry() {
push(to, state);
}
// 忽略其他類似的程式碼
if (allowTx(nextAction, nextLocation, retry)) {
index += 1;
// 別處是呼叫原生 API, history.pushState
entries.splice(index, entries.length, nextLocation);
applyTx(nextAction, nextLocation);
}
}
// 與 push 類似, 忽略 replace
function go(delta: number) {
// 與HashHistory不同, 也是走的類似 push
let nextIndex = clamp(index + delta, 0, entries.length - 1);
let nextAction = Action.Pop;
let nextLocation = entries[nextIndex];
function retry() {
go(delta);
}
if (allowTx(nextAction, nextLocation, retry)) {
index = nextIndex;
applyTx(nextAction, nextLocation);
}
}
let history: MemoryHistory = {
// 基本相同
};
return history;
}
Navigate
用來改變 當然 location 的方法, 是一個 react-router 丟擲的 API
使用方式:
function App() {
// 一旦 user 是有值的, 就跳轉至 `/dashboard` 頁面了
// 算是跳轉路由的一種方案
return <div>
{user && (
<Navigate to="/dashboard" replace={true} />
)}
<form onSubmit={event => this.handleSubmit(event)}>
<input type="text" name="username" />
<input type="password" name="password" />
</form>
</div>
}
原始碼
export function Navigate({ to, replace, state }: NavigateProps): null {
// 直接呼叫 useNavigate 來獲取 navigate 方法, 並且 useEffect 每次都會觸發
// useNavigate 原始碼在下方會講到
let navigate = useNavigate();
React.useEffect(() => {
navigate(to, { replace, state });
});
return null;
}
Outlet
用來渲染子路由的元素, 簡單來說就是一個路由的佔位符
程式碼很簡單, 使用的邏輯是這樣
使用方式:
function App(props) {
return (
<HashRouter>
<Routes>
<Route path={'/'} element={<Dashboard></Dashboard>}>
<Route path="qqwe" element={<About/>}/>
<Route path="about" element={<About/>}/>
<Route path="users" element={<Users/>}/>
</Route>
</Routes>
</HashRouter>
);
}
// 其中外層的Dashboard:
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
// 這裡就會渲染他的子路由了
// 和以前 children 差不多
</div>
);
}
原始碼
export function Outlet(props: OutletProps): React.ReactElement | null {
return useOutlet(props.context);
}
export function useOutlet(context?: unknown): React.ReactElement | null {
let outlet = React.useContext(RouteContext).outlet;
if (outlet) {
return (
<OutletContext.Provider value={context}>{outlet}</OutletContext.Provider>
);
}
return outlet;
}
useParams
從當前URL所匹配的路徑中, 返回一個物件的鍵/值對的動態引數。
function useParams<
ParamsOrKey extends string | Record<string, string | undefined> = string
>(): Readonly<
[ParamsOrKey] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey>
> {
// 直接獲取了 RouteContext 中 matches 陣列的最後一個物件, 如果沒有就是空物件
let { matches } = React.useContext(RouteContext);
let routeMatch = matches[matches.length - 1];
return routeMatch ? (routeMatch.params as any) : {};
}
useResolvedPath
將給定的`to'值的路徑名與當前位置進行比較
在 <NavLink>
這個元件中使用到
function useResolvedPath(to: To): Path {
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
// 合併成一個 json 字元, 至於為什麼又要解析, 是為了新增字元層的快取, 如果是一個物件, 就不好淺比較了
let routePathnamesJson = JSON.stringify(
matches.map(match => match.pathnameBase)
);
// resolveTo 的具體作用在下方討論
return React.useMemo(
() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname),
[to, routePathnamesJson, locationPathname]
);
}
useRoutes
useRoutes鉤子的功能等同於<Routes>,但它使用JavaScript物件而不是<Route>元素來定義路由。
相當於是一種 schema 版本, 更好的配置性
使用方式:
如果使用過 umi, 是不是會感覺到一模一樣
function App() {
let element = useRoutes([
{ path: "/", element: <Home /> },
{ path: "dashboard", element: <Dashboard /> },
{
path: "invoices",
element: <Invoices />,
children: [
{ path: ":id", element: <Invoice /> },
{ path: "sent", element: <SentInvoices /> }
]
},
{ path: "*", element: <NotFound /> }
]);
return element;
}
原始碼
// 具體的 routes 物件是如何生成的, 下面的 Routes-createRoutesFromChildren 會講到
export function useRoutes(
routes: RouteObject[],
locationArg?: Partial<Location> | string
): React.ReactElement | null {
let { matches: parentMatches } = React.useContext(RouteContext);
let routeMatch = parentMatches[parentMatches.length - 1];
// 獲取匹配的 route
let parentParams = routeMatch ? routeMatch.params : {};
let parentPathname = routeMatch ? routeMatch.pathname : "/";
let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
let parentRoute = routeMatch && routeMatch.route;
// 這裡上面都是一些引數, 沒有就是預設值
// 等於 React.useContext(LocationContext).location, 約等於原生的 location
let locationFromContext = useLocation();
let location;
if (locationArg) { // 對於配置項引數的一些判斷
let parsedLocationArg =
typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
location = parsedLocationArg;
} else {
location = locationFromContext;
}
// 如果引數裡有則使用引數裡的, 如果沒有使用 context 的
let pathname = location.pathname || "/";
let remainingPathname =
parentPathnameBase === "/"
? pathname
: pathname.slice(parentPathnameBase.length) || "/";
// matchRoutes 大概的作用是通過pathname遍歷尋找,匹配到的路由 具體原始碼放在下面講
let matches = matchRoutes(routes, { pathname: remainingPathname });
// 最後呼叫渲染函式 首先對資料進行 map
// joinPaths 的作用約等於 paths.join("/") 並且去除多餘的斜槓
return _renderMatches(
matches &&
matches.map(match =>
Object.assign({}, match, {
params: Object.assign({}, parentParams, match.params),
pathname: joinPaths([parentPathnameBase, match.pathname]),
pathnameBase:
match.pathnameBase === "/"
? parentPathnameBase
: joinPaths([parentPathnameBase, match.pathnameBase])
})
),
parentMatches
);
}
useRoutes-matchRoutes
function matchRoutes(
routes: RouteObject[],
locationArg: Partial<Location> | string,
basename = "/"
): RouteMatch[] | null {
let location =
typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
// 獲取排除 basename 的 pathname
let pathname = stripBasename(location.pathname || "/", basename);
if (pathname == null) {
return null;
}
// flattenRoutes 函式的主要作用, 壓平 routes, 方便遍歷
// 原始碼見下方
let branches = flattenRoutes(routes);
// 對路由進行排序
// rankRouteBranches 原始碼見下方
rankRouteBranches(branches);
// 篩選出匹配到的路由 matchRouteBranch原始碼在下面講
let matches = null;
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
useRoutes-matchRoutes-stripBasename
拆分 basename, 程式碼很簡單, 這裡就直接貼出來了
function stripBasename(pathname: string, basename: string): string | null {
if (basename === "/") return pathname;
if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
return null;
}
let nextChar = pathname.charAt(basename.length);
if (nextChar && nextChar !== "/") {
return null;
}
return pathname.slice(basename.length) || "/";
}
useRoutes-matchRoutes-flattenRoutes
遞迴處理 routes, 壓平 routes
function flattenRoutes(
routes: RouteObject[],
branches: RouteBranch[] = [],
parentsMeta: RouteMeta[] = [],
parentPath = ""
): RouteBranch[] {
routes.forEach((route, index) => {
let meta: RouteMeta = {
relativePath: route.path || "",
caseSensitive: route.caseSensitive === true,
childrenIndex: index,
route
};
if (meta.relativePath.startsWith("/")) {
meta.relativePath = meta.relativePath.slice(parentPath.length);
}
// joinPaths 原始碼: (paths)=>paths.join("/").replace(/\/\/+/g, "/")
// 把陣列轉成字串, 並且清除重複斜槓
let path = joinPaths([parentPath, meta.relativePath]);
let routesMeta = parentsMeta.concat(meta);
// 如果有子路由則遞迴
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
// 匹配不到就 return
if (route.path == null && !route.index) {
return;
}
// 壓平後元件新增的物件
branches.push({ path, score: computeScore(path, route.index), routesMeta });
});
return branches;
}
useRoutes-matchRoutes-rankRouteBranches
對路由進行排序, 這裡可以略過,不管排序演算法如何, 只需要知道, 知道輸入的值是經過一系列排序的就行
function rankRouteBranches(branches: RouteBranch[]): void {
branches.sort((a, b) =>
a.score !== b.score
? b.score - a.score // Higher score first
: compareIndexes(
a.routesMeta.map(meta => meta.childrenIndex),
b.routesMeta.map(meta => meta.childrenIndex)
)
);
}
useRoutes-matchRoutes-matchRouteBranch
匹配函式, 接受引數 branch 就是某一個 rankRouteBranches
function matchRouteBranch<ParamKey extends string = string>(
branch: RouteBranch,
pathname: string
): RouteMatch<ParamKey>[] | null {
let { routesMeta } = branch;
let matchedParams = {};
let matchedPathname = "/";
let matches: RouteMatch[] = [];
// routesMeta 詳細來源可以檢視 上面的flattenRoutes
for (let i = 0; i < routesMeta.length; ++i) {
let meta = routesMeta[i];
let end = i === routesMeta.length - 1;
let remainingPathname =
matchedPathname === "/"
? pathname
: pathname.slice(matchedPathname.length) || "/";
// 比較, matchPath 原始碼在下方
let match = matchPath(
{ path: meta.relativePath, caseSensitive: meta.caseSensitive, end },
remainingPathname
);
// 如果返回是空 則直接返回
if (!match) return null;
// 更換物件源
Object.assign(matchedParams, match.params);
let route = meta.route;
// push 到最終結果上, joinPaths 不再贅述
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
route
});
if (match.pathnameBase !== "/") {
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
}
return matches;
}
useRoutes-matchRoutes-matchRouteBranch-matchPath
對一個URL路徑名進行模式匹配,並返回有關匹配的資訊。
他也是一個保留在外的可用 API
export function matchPath<
ParamKey extends ParamParseKey<Path>,
Path extends string
>(
pattern: PathPattern<Path> | Path,
pathname: string
): PathMatch<ParamKey> | null {
// pattern 的重新賦值
if (typeof pattern === "string") {
pattern = { path: pattern, caseSensitive: false, end: true };
}
// 通過正則匹配返回匹配到的正規表示式 matcher 為 RegExp
let [matcher, paramNames] = compilePath(
pattern.path,
pattern.caseSensitive,
pattern.end
);
// 正則物件的 match 方法
let match = pathname.match(matcher);
if (!match) return null;
// 取 match 到的值
let matchedPathname = match[0];
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
let captureGroups = match.slice(1);
// params 轉成物件 { param:value, ... }
let params: Params = paramNames.reduce<Mutable<Params>>(
(memo, paramName, index) => {
// 如果是*號 轉換
if (paramName === "*") {
let splatValue = captureGroups[index] || "";
pathnameBase = matchedPathname
.slice(0, matchedPathname.length - splatValue.length)
.replace(/(.)\/+$/, "$1");
}
// safelyDecodeURIComponent 等於 decodeURIComponent + try_catch
memo[paramName] = safelyDecodeURIComponent(
captureGroups[index] || "",
paramName
);
return memo;
},
{}
);
return {
params,
pathname: matchedPathname,
pathnameBase,
pattern
};
}
useRoutes-matchRoutes-matchRouteBranch-matchPath-compilePath
function compilePath(
path: string,
caseSensitive = false,
end = true
): [RegExp, string[]] {
let paramNames: string[] = [];
// 正則匹配替換
let regexpSource =
"^" +
path
// 忽略尾隨的 / 和 /*
.replace(/\/*\*?$/, "")
// 確保以 / 開頭
.replace(/^\/*/, "/")
// 轉義特殊字元
.replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
.replace(/:(\w+)/g, (_: string, paramName: string) => {
paramNames.push(paramName);
return "([^\\/]+)";
});
// 對於*號的特別判斷
if (path.endsWith("*")) {
paramNames.push("*");
regexpSource +=
path === "*" || path === "/*"
? "(.*)$" // Already matched the initial /, just match the rest
: "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
} else {
regexpSource += end
? "\\/*$" // 匹配到末尾時,忽略尾部斜槓
:
"(?:\\b|\\/|$)";
}
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
// 返回匹配結果
return [matcher, paramNames];
}
useRoutes-_renderMatches
渲染匹配到的路由
function _renderMatches(
matches: RouteMatch[] | null,
parentMatches: RouteMatch[] = []
): React.ReactElement | null {
if (matches == null) return null;
// 通過 context 傳遞資料
return matches.reduceRight((outlet, match, index) => {
return (
<RouteContext.Provider
children={
match.route.element !== undefined ? match.route.element : <Outlet />
}
value={{
outlet,
matches: parentMatches.concat(matches.slice(0, index + 1))
}}
/>
);
}, null as React.ReactElement | null);
}
Router
為應用程式的其他部分提供context資訊
通常不會使用此元件, 他是 MemoryRouter 最終渲染的元件
在 react-router-dom 庫中, 也是 BrowserRouter 和 HashRouter 的最終渲染元件
export function Router({
basename: basenameProp = "/",
children = null,
location: locationProp,
navigationType = NavigationType.Pop,
navigator,
static: staticProp = false
}: RouterProps): React.ReactElement | null {
// 格式化 baseName
let basename = normalizePathname(basenameProp);
// memo context value
let navigationContext = React.useMemo(
() => ({ basename, navigator, static: staticProp }),
[basename, navigator, staticProp]
);
// 如果是字串則解析 根據 #, ? 特殊符號解析 url
if (typeof locationProp === "string") {
locationProp = parsePath(locationProp);
}
let {
pathname = "/",
search = "",
hash = "",
state = null,
key = "default"
} = locationProp;
// 同樣的快取
let location = React.useMemo(() => {
// 這還方法在 useRoutes-matchRoutes-stripBasename 講過這裡就不多說
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) {
return null;
}
return {
pathname: trailingPathname,
search,
hash,
state,
key
};
}, [basename, pathname, search, hash, state, key]);
// 空值判斷
if (location == null) {
return null;
}
// 提供 context 的 provider, 傳遞 children
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location, navigationType }}
/>
</NavigationContext.Provider>
);
}
parsePath
此原始碼來自於 history 倉庫
function parsePath(path: string): Partial<Path> {
let parsedPath: Partial<Path> = {};
// 首先確定 path
if (path) {
// 是否有#號 , 如果有則擷取
let hashIndex = path.indexOf('#');
if (hashIndex >= 0) {
parsedPath.hash = path.substr(hashIndex);
path = path.substr(0, hashIndex);
}
// 再判斷 ? , 有也擷取
let searchIndex = path.indexOf('?');
if (searchIndex >= 0) {
parsedPath.search = path.substr(searchIndex);
path = path.substr(0, searchIndex);
}
// 最後就是 path
if (path) {
parsedPath.pathname = path;
}
}
// 返回結果
return parsedPath;
}
Routes
用來包裹 route 的元素, 主要是通過 useRoutes 的邏輯
function Routes({
children,
location
}: RoutesProps): React.ReactElement | null {
return useRoutes(createRoutesFromChildren(children), location);
}
Routes-createRoutesFromChildren
接收到的引數一般都是 Route children, 可能是多層巢狀的, 最後得的我們定義的 route 元件結構,
它將被傳遞給 useRoutes 函式
function createRoutesFromChildren(
children: React.ReactNode
): RouteObject[] {
let routes: RouteObject[] = [];
// 使用官方函式迴圈
React.Children.forEach(children, element => {
if (element.type === React.Fragment) {
// 如果是 React.Fragment 元件 則直接push 遞迴函式
routes.push.apply(
routes,
createRoutesFromChildren(element.props.children)
);
return;
}
let route: RouteObject = {
caseSensitive: element.props.caseSensitive,
element: element.props.element,
index: element.props.index,
path: element.props.path
}; // route 物件具有的屬性
// 同樣地遞迴
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
useHref
返回完整的連結
export function useHref(to: To): string {
let { basename, navigator } = React.useContext(NavigationContext);
// useResolvedPath 在上面講過
let { hash, pathname, search } = useResolvedPath(to);
let joinedPathname = pathname;
if (basename !== "/") {
let toPathname = getToPathname(to);
let endsWithSlash = toPathname != null && toPathname.endsWith("/");
joinedPathname =
pathname === "/"
? basename + (endsWithSlash ? "/" : "")
: joinPaths([basename, pathname]);
}
// 可以看做, 路由的拼接, 包括 ? , #
return navigator.createHref({ pathname: joinedPathname, search, hash });
}
resolveTo
解析toArg, 返回物件
function resolveTo(
toArg: To,
routePathnames: string[],
locationPathname: string
): Path {
// parsePath上面已經分析過了
let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname;
let from: string;
if (toPathname == null) {
from = locationPathname;
} else {
let routePathnameIndex = routePathnames.length - 1;
// 如果以 .. 開始的路徑
if (toPathname.startsWith("..")) {
let toSegments = toPathname.split("/");
// 去除 ..
while (toSegments[0] === "..") {
toSegments.shift();
routePathnameIndex -= 1;
}
to.pathname = toSegments.join("/");
}
// from 複製
from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
}
// 解析, 返回物件
let path = resolvePath(to, from);
if (
toPathname &&
toPathname !== "/" &&
toPathname.endsWith("/") &&
!path.pathname.endsWith("/")
) {
path.pathname += "/";
}
// 確保加上末尾 /
return path;
}
resolveTo-resolvePath
返回一個相對於給定路徑名的解析路徑物件, 這裡的函式也基本都講過
function resolvePath(to: To, fromPathname = "/"): Path {
let {
pathname: toPathname,
search = "",
hash = ""
} = typeof to === "string" ? parsePath(to) : to;
let pathname = toPathname
? toPathname.startsWith("/")
? toPathname
// resolvePathname
: resolvePathname(toPathname, fromPathname)
: fromPathname;
return {
pathname,
search: normalizeSearch(search),
hash: normalizeHash(hash)
};
}
resolveTo-resolvePath-resolvePathname
function resolvePathname(relativePath: string, fromPathname: string): string {
// 去除末尾斜槓, 再以斜槓分割成陣列
let segments = fromPathname.replace(/\/+$/, "").split("/");
let relativeSegments = relativePath.split("/");
relativeSegments.forEach(segment => {
if (segment === "..") {
// 移除 ..
if (segments.length > 1) segments.pop();
} else if (segment !== ".") {
segments.push(segment);
}
});
return segments.length > 1 ? segments.join("/") : "/";
}
useLocation useNavigationType
function useLocation(): Location {
// 只是獲取 context 中的資料
return React.useContext(LocationContext).location;
}
同上
function useNavigationType(): NavigationType {
return React.useContext(LocationContext).navigationType;
}
useMatch
function useMatch<
ParamKey extends ParamParseKey<Path>,
Path extends string
>(pattern: PathPattern<Path> | Path): PathMatch<ParamKey> | null {
// 獲取 location.pathname
let { pathname } = useLocation();
// matchPath 在 useRoutes-matchRoutes-matchRouteBranch-matchPath 中講到過
// 對一個URL路徑名進行模式匹配,並返回有關匹配的資訊。
return React.useMemo(
() => matchPath<ParamKey, Path>(pattern, pathname),
[pathname, pattern]
);
}
useNavigate
此 hooks 是用來獲取操作路由物件的
function useNavigate(): NavigateFunction {
// 從 context 獲取資料
let { basename, navigator } = React.useContext(NavigationContext);
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
// 轉成 json, 方便 memo 對比
let routePathnamesJson = JSON.stringify(
matches.map(match => match.pathnameBase)
);
let activeRef = React.useRef(false);
React.useEffect(() => {
activeRef.current = true;
}); // 控制渲染, 需要在渲染完畢一次後操作
// 路由操作函式
let navigate: NavigateFunction = React.useCallback(
(to: To | number, options: NavigateOptions = {}) => {
if (!activeRef.current) return; // 控制渲染
// 如果 go 是數字, 則結果類似於 go 方法
if (typeof to === "number") {
navigator.go(to);
return;
}
// 解析go
let path = resolveTo(
to,
JSON.parse(routePathnamesJson),
locationPathname
);
if (basename !== "/") {
path.pathname = joinPaths([basename, path.pathname]);
}
// 這一塊 就是 前一個括號產生函式, 後一個括號傳遞引數
// 小小地轉換下:
// !!options.replace ?
// navigator.replace(
// path,
// options.state
// )
// : navigator.push(
// path,
// options.state
// )
//
(!!options.replace ? navigator.replace : navigator.push)(
path,
options.state
);
},
[basename, navigator, routePathnamesJson, locationPathname]
);
// 最後返回
return navigate;
}
generatePath
返回一個有引數插值的路徑。 原理還是通過正則替換
function generatePath(path: string, params: Params = {}): string {
return path
.replace(/:(\w+)/g, (_, key) => {
return params[key]!;
})
.replace(/\/*\*$/, _ =>
params["*"] == null ? "" : params["*"].replace(/^\/*/, "/")
);
}
他的具體使用:
generatePath("/users/:id", { id: 42 }); // "/users/42"
generatePath("/files/:type/*", {
type: "img",
"*": "cat.jpg"
}); // "/files/img/cat.jpg"
這裡的程式碼可以說是覆蓋整個 react-router 80%以上, 有些簡單的, 用處小的這裡也不再過多贅述了