新聞中心
本文是以 React 結(jié)合 React-Router 實(shí)現(xiàn),vue的實(shí)現(xiàn)思路基本一致,只有后綴名和 vue-router的差別,需要的可以照搬此方案。

為企業(yè)提供成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、網(wǎng)站優(yōu)化、營銷型網(wǎng)站建設(shè)、競價托管、品牌運(yùn)營等營銷獲客服務(wù)。成都創(chuàng)新互聯(lián)公司擁有網(wǎng)絡(luò)營銷運(yùn)營團(tuán)隊(duì),以豐富的互聯(lián)網(wǎng)營銷經(jīng)驗(yàn)助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營銷獲客難題,做到“讓獲客更簡單”。自創(chuàng)立至今,成功用技術(shù)實(shí)力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營銷”三大難題,同時降低了營銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!
路由形式
首先看看 Next.js 基于文件約定式路由長什么樣。Next.js將文件添加到 pages 目錄時,它會自動生成對應(yīng)的路由。在開發(fā)時省去了很多模板代碼,提升開發(fā)效率。
特性一:它將 index 文件名 js|jsx|ts|tsx 結(jié)尾的文件,映射成當(dāng)前目錄的根路由:
- pages/index.js → /
- pages/blog/index.js → /blog
特性二:支持嵌套目錄文件。如果創(chuàng)建嵌套文件夾結(jié)構(gòu),文件將自動以相同的方式生成路由:
- pages/about.js → /about
- pages/blog/first-post.js → /blog/first-post
- pages/dashboard/settings/username.js → /dashboard/settings/username
特性三:使用括號語法。匹配動態(tài)命名參數(shù):
- pages/blog/[slug].js → /blog/:slug( /blog/hello-world)
- pages/[username]/settings.js → /:username/settings( /foo/settings)
這種路由方式看起來非常清晰,創(chuàng)建一個路由就如同寫組件一樣簡單。umijs 也支持約定式路由,形式基本一致,用過的想必也因此受益。然而 Vite作為一個腳手架提供更加通用的功能以支持 vue和 react,自然不會耦合這種路由方案。
啟發(fā)
在 Vite 官方文檔中 https://cn.vitejs.dev/guide/features.html#glob-import Glob 導(dǎo)入是這樣介紹的:
Vite 支持使用特殊的 import.meta.glob 函數(shù)從文件系統(tǒng)導(dǎo)入多個模塊:
const modules = import.meta.glob('./dir/*.js');以上將會被轉(zhuǎn)譯為下面的樣子:
const modules = {
'./dir/foo.js': () => import('./dir/foo.js'),
'./dir/bar.js': () => import('./dir/bar.js'),
};這個 API 就類似 Webpack 的require.context()。Nice. 可以來個大膽的想法,用 React.lazy 結(jié)合 React-Router v6 做個文件約定式路由。說做就做!我們需要做的事情只有一件,那就是將這個從文件讀取出來的 JSON 轉(zhuǎn)換為 React-Router 配置。
先看一下 React-Router v6 的結(jié)構(gòu)長這樣:
}>
} />
}>
} />
} />
} />
還有個 useRoutes 以 JSON 的形式來配置路由:
const routes = [
{
element:,
path: '/',
children: [
{
index: true,
element:,
},
{
path: 'teams',
element:,
children: [
{
index: true,
element:,
},
{
path: ':teamId',
element:,
},
{
path: 'new',
element:,
},
],
},
],
},
];
// 導(dǎo)出路由組件
export function PageRoutes() {
return useRoutes(routes);
}
這樣只需要轉(zhuǎn)換成以上 JSON 結(jié)構(gòu)就可以了。
路由規(guī)則
生成的方式,我們盡量與 next.js 保持一致, 并實(shí)現(xiàn) umijs 形式的約定式 layout。但避免一個問題:避免將不需要的組件映射成路由。這點(diǎn) next.js 必須將非路由相關(guān)的文件放到 pages 目錄之外。而 umijs 的排除規(guī)則是這樣的:
- 以 . 或 _ 開頭的文件或目錄
- 以 d.ts 結(jié)尾的類型定義文件
- 以 test.ts、spec.ts、e2e.ts 結(jié)尾的測試文件(適用于 .js、.jsx 和 .tsx 文件)
- components 和 component 目錄
- utils 和 util 目錄
- 不是 .js、.jsx、.ts 或 .tsx 文件
- 文件內(nèi)容不包含 JSX 元素
這點(diǎn) umijs 確實(shí)做得有點(diǎn)復(fù)雜多余了,一大堆規(guī)則很容易讓開發(fā)者暈頭轉(zhuǎn)向。在組件化的項(xiàng)目中,路由文件很多情況下會遠(yuǎn)少于頁面組件。我們可以使用某種特殊標(biāo)識,標(biāo)明它是一個路由:
我們暫定 $ 開頭的文件作為路由生成的規(guī)則
- pages/$index.tsx → /
- pages/blog/$index.tsx → /blog
- pages/$about.tsx → /about
- pages/blog/$[foo].tsx → /blog/:foo( /blog/hello-world)
用 $.tsx 作為 layout 而不是 umijs 中的 _layout.tsx。
在 fast-glob https://github.com/mrmlnc/fast-glob#pattern-syntax 詳細(xì)文檔中支持更多用法,我們則需要讀取 pages 目錄下的所有 ts、tsx 文件,通配符可以這樣寫:
const modules = import.meta.glob('/src/pages/**/$*.{ts,tsx}');我們有這樣一個目錄
├─pages
│ │ $.tsx
│ │ $index.tsx
│ │
│ └─demo
│ │ $index.tsx
│ │
│ └─demo-child
│ $hello-world.tsx
│ $index.tsx
│ $[name].tsx
打印 modules 結(jié)果如下:
實(shí)現(xiàn)
我們可以先將 modules 變量轉(zhuǎn)換為嵌套結(jié)構(gòu)的 JSON 便于理解(先忽略 $.tsx):
import { set } from 'lodash-es';
/**
* 根據(jù) pages 目錄生成路徑配置
*/
function generatePathConfig(): Record {
// 掃描 src/pages 下的所有具有路由文件
const modules = import.meta.glob('/src/pages/**/$*.{ts,tsx}');
const pathConfig = {};
Object.keys(modules).forEach((filePath) => {
const routePath = filePath
// 去除 src/pages 不相關(guān)的字符
.replace('/src/pages/', '')
// 去除文件名后綴
.replace(/.tsx?/, '')
// 轉(zhuǎn)換動態(tài)路由 $[foo].tsx => :foo
.replace(/\$\[([\w-]+)]/, ':$1')
// 轉(zhuǎn)換以 $ 開頭的文件
.replace(/\$([\w-]+)/, '$1')
// 以目錄分隔
.split('/');
// 使用 lodash.set 合并為一個對象
set(pathConfig, routePath, modules[filePath]);
});
return pathConfig;
} 打印的 generatePathConfig() 目錄結(jié)構(gòu)結(jié)果如下:
現(xiàn)在已經(jīng)很接近 React-Router 的配置了。
我們只需要將 import() 語法稍微封裝一下 () => import('./demo/index.tsx') 基礎(chǔ)上包一層 React.lazy 將其轉(zhuǎn)換為組件:
/**
* 為動態(tài) import 包裹 lazy 和 Suspense
*/
function wrapSuspense(importer: () => Promise<{ default: ComponentType }>) {
if (!importer) {
return undefined;
}
// 使用 React.lazy 包裹 () => import() 語法
const Component = lazy(importer);
// 結(jié)合 Suspense,這里可以自定義 loading 組件
return (
);
}
我們將 pathConfig 遞歸將其轉(zhuǎn)換為 React-Router 的配置
/**
* 將文件路徑配置映射為 react-router 路由
*/
function mapPathConfigToRoute(cfg: Record): RouteObject[] {
// route 的子節(jié)點(diǎn)為數(shù)組
return Object.entries(cfg).map(([routePath, child]) => {
// () => import() 語法判斷
if (typeof child === 'function') {
// 等于 index 則映射為當(dāng)前根路由
const isIndex = routePath === 'index';
return {
index: isIndex,
path: isIndex ? undefined : routePath,
// 轉(zhuǎn)換為組件
element: wrapSuspense(child),
};
}
// 否則為目錄,則查找下一層級
const { $, ...rest } = child;
return {
path: routePath,
// layout 處理
element: wrapSuspense($),
// 遞歸 children
children: mapPathConfigToRoute(rest),
};
});
}
最后組裝這個配置:
function generateRouteConfig(): RouteObject[] {
const { $, ...pathConfig } = generatePathConfig();
// 提取跟路由的 layout
return [
{
path: '/',
element: wrapSuspense($),
children: mapPathConfigToRoute(pathConfig),
},
];
}
const routeConfig = generateRouteConfig();打印這個 routeConfig 配置試試:
最后將封裝的組件插入到 App 中
export function PageRoutes() {
return useRoutes(routeConfig);
}至于為什么要將 PageRoutes 單獨(dú)做成個組件,因?yàn)?useRoutes 需要 BrowserRouter 的 Context,否則會報錯。
function App() {
return (
);
}大功告成!預(yù)覽一下:
結(jié)語
想起幾年前寫 React-Router v2 配置 JSON 的痛苦經(jīng)歷歷歷在目?,F(xiàn)在有了基于文件式路由用法,在Vite 上面也能愉快地早點(diǎn)下班了。
網(wǎng)站名稱:Vite約定式路由的優(yōu)秀實(shí)踐
網(wǎng)站路徑:http://m.fisionsoft.com.cn/article/cojijci.html


咨詢
建站咨詢
