阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!

免責聲明:本故事主要講述我的經歷。為了流程和參與度,我跳過了詳細的程式碼解釋和一些技術細節。這裡的目標是分享旅程,而不是提供深入的技術細分。

簡介:謙遜與 TypeScript 魔法的一課

每個開發人員都會有這樣的時刻——他們以為自己掌握了一些東西,但卻被同事徹底打敗了。這就是此刻的故事。它始於一個簡單的目標:改善 React 專案中的路由。最終我開始質疑自己身為開發人員的整個存在。 😅

接下來的旅程充滿了興奮、沮喪、純粹由多巴胺推動的突破時刻,最終,對經驗、技能以及成為高級開發人員的真正意義有了令人大開眼界的認識。係好安全帶。 🚀


設定🎬

這又是發展的一天,又是覺得自己像個專業人士的一天。我正在開發一個 React 專案,使用createBrowserRouter來處理Router.tsx中的路由。注意到了.tsx副檔名了嗎?是的,這是因為createBrowserRouter需要element內部的實際 JSX 元件。讓我給你展示一下路由器的範例:

export const router = createBrowserRouter([
    {
        path: "/",
        element: <AppLayout/>,
        children: [
            {
                path: "",
                element: <ProtectedRoute><DashboardLayout/></ProtectedRoute>,
                children: [
                    {
                        index: true,
                        path: "projects",
                        element: <OnlyAdmin fallback="/all-projects"><Projects/></OnlyAdmin>,
                    },
                    {
                        path: "projects/:projectId",
                        element: <OnlyAdmin><ProjectGuard/></OnlyAdmin>,
                        children: [
                            {
                                path: "groups",
                                element: <ProjectGroups/>
                            },
                            {
                                id: "project-stats",
                                path: "groups/:groupId",
                                element: <GroupGuard/>,
                                children: [
                                    {
                                        path: "stats",
                                        element: <GroupStats/>
                                    },
                                    {
                                        path: "consents",
                                        element: <Consents/>
                                    }
                                ]
                            },
                            {
                                path: "stats",
                                element: <ProjectStats/>
                            }
                        ]
                    },
                    fallbackRoute("/projects")
                ]
            }
        ]
    },
    {
        path: "*",
        element: <NoMatch/>,
    }
], {
    future: {
        v7_startTransition: true,
    }
});

喜歡使用createBrowserRouter 。它很簡潔,允許嵌套路由,如果你使用 React Router,我強烈推薦它。但問題隨之而來


問題🤯

在整個專案中,我們使用<Link>元件進行導航。很標準,對吧?類似:

<Link to="/projects/123/groups/456">Go to Group</Link>

現在,混亂就開始了。客戶請求改變路由結構。只需對路徑進行一些小的調整,沒有什麼大問題……對嗎?錯誤的。

因為現在,專案中的每個手動編寫的 URL 都必須更新。 🥲

我盯著程式碼庫,心想:好吧,這很糟糕。這真的很糟糕。

一定有更好的方法——一種不依賴硬編碼字串來驗證整個應用程式的連結的方法、一種在編寫路線時自動完成路線的方法、一種確保一致性的方法。

我的腦力激盪模式啟動了。

🔍我怎麼能讓路線變得更聰明?

💡 TypeScript 可以幫助強制執行路線驗證嗎?

🤔如何讓 WebStorm 自動完成路線名稱?

然後,一個最愚蠢但最有趣的想法突然出現在我腦海中:我應該為此製作一個 JetBrains 外掛程式嗎? 😂

整整五分鐘的時間裡,我都在認真考慮寫一個完整的 JetBrains 外掛來解決我的路由問題。但後來我搖了搖頭。希赫姆,停下來。公司不會付錢給你來開發無用的插件。


「絕妙」的計劃

相反,我想:讓我們使用 TypeScript。

如果我能以某種方式將路由路徑轉換為TypeScript 強制的 ID ,那麼我可以:

✔ 驗證連結。

✔ 啟用自動完成功能。

✔ 確保路線始終正確,不再有斷開的連結。

我修改了路由器,使每條路由包含唯一的 ID

const routeObjects: RouteObject[] = [
    {
        path: "/",
        element: <AppLayout/>,
        id: "root",
        children: [
            {
                path: "projects",
                element: <OnlyAdmin><Projects/></OnlyAdmin>,
                id: "admin-projects-list"
            }
        ]
    }
];

覺得自己像個天才。但是TypeScript 並不配合

我需要 TypeScript動態地辨識這些 ID作為有效值。我嘗試使用typeof routesObject ,但沒有用。經過數小時搜尋 TypeScript 文件(順便說一句,並不是很好的文件)、大量Stack Overflow 頁面,甚至與 ChatGPT 進行腦力激盪後,我還是一無所獲。

這時,一個新的想法出現了:如果我動態產生一個 TypeScript 類型檔案會怎麼樣?

繁榮。新計劃:

  1. 將所有路線提取到一個物件中。

  2. 動態產生TypeScript類型檔案。

  3. 每當路線改變時執行腳本。

我寫了一個腳本:

npm run generate-router-types

輸出結果如下:

export type AppRoute = 'admin-projects-list' | 'home';

💥任務完成!

我推動了 PR,休息了一會兒,然後回來期待能夠盡快獲得批准


「喬」時刻😐

然後我看到了喬的評論。我們叫他喬吧(這不是他的真名,但我們就這樣叫吧)。

“我有更好的方法,只使用 TypeScript”

github PR 評論截圖

我的靈魂離開了我的身體。 💀

兄弟,你以為我沒有先嘗試過嗎?

然後他直接傳訊息給我:

“您有 15 分鐘的時間來討論一下您的 PR 嗎?”

偉大的。一次會議。和喬一起。

Joe 是公司新人,但他的履歷很瘋狂——Rust、Go、Node、DevOps,應有盡有。但他也擁有那種活力──那種對任何事總是能找到反駁的開發者。

儘管如此,我還是抱著「隨便」的態度參加了通話。讓我們來聽聽這位天才的看法。


羞辱

Joe 分享了他的螢幕。

他開始解釋。

我看到了他的程式碼。

就在那一刻,我意識到......事實上,我還只是個大三學生。 😂

他的解決方案?純粹的TypeScript 魔法。

  • 沒有建置腳本。

  • 無需外部工具。

  • 沒有路線提取功能。

  • 僅需幾行 TypeScript。

type MergePath<T extends string | undefined, P extends string> =
    T extends ""
    ? P
    : P extends "/"|""
    ? `${P}${T}`
    : `${P}/${T}`;

type ExtractRouteType<T extends RouteObject, P extends string> =
    T extends { path: string } ?
    { route: P, paramsKeys: ExtractPathParams<P> }
    : never;
type ExtractRoute<T extends RouteObject, P extends string> = (T extends { id: string, path: string } ? { [K in T["id"]]: ExtractRouteType<T, P> } : {}) & ExtractRoutes<T["children"], P>;
type ExtractRoutes<T extends RouteObject[] | undefined, P extends string = ""> = T extends [infer R extends RouteObject, ...infer S extends RouteObject[]] ? ExtractRoute<R, MergePath<R["path"], P>> & ExtractRoutes<S, P> : {};
export type RouteById = ExtractRoutes<typeof routeObjects> extends infer U ? { [K in keyof U]: U[K] } : never;

😭😭😭

我花了幾個小時尋找一個根本不需要的解決方案。

Joe 笑著說: “是的,當你知道如何使用 TypeScript 時,它真的很強大。”

是的,我受教育了。


救贖之弧🦸‍♂️

但我非但沒有生悶氣,反而進行了反擊。我採用了 Joe 的 TypeScript 魔法並對其進行了改進

我想:如果他可以動態提取路線,為什麼我不能動態驗證參數?

我希望TypeScript 強制執行路由參數,這表示:

✔ 如果路線需要:projectId ,我應該被迫傳遞{ projectId: string }作為參數。

✔ 如果我忘記了一個參數,TypeScript 應該會拋出一個錯誤

✔ 如果我新增了不需要的額外參數, TypeScript 會抱怨

✔ 當然,自動完成功能仍應完美運作。

我回到了我的戰鬥崗位。 💻☕

我介紹了ExtractPathParams ,這是一個 TypeScript 實用程序,用於僅從路由路徑中提取所需的參數

type ExtractPathParams<Path extends string> =
    Path extends `${string}:${infer Param}/${infer Rest}`
    ? [Param, ...ExtractPathParams<`/${Rest}`>]
    : Path extends `${string}:${infer Param}`
    ? [Param]
    : [];

然後,我重寫了buildRoute ,以便如果沒有傳遞必需的參數,它就會拒絕編譯

export function buildRoute<RouteId extends keyof RouteById>(
    routeId: RouteId,
    ...params: ParamsArg<RouteId> extends undefined ? [] : [ParamsArg<RouteId>]
): string {
    if (!routes[routeId]) {
        throw new Error(`Route ${routeId} not found`);
    }
    let route = routes[routeId].route as string;
    let params_ = params.length > 0 ? params[0] : {};
    for (const [key, value] of Object.entries(params_)) {
        route = route.replace(`:${key}`, value as string);
    }
    if (route.includes(':')) {
        throw new Error(`Route ${routeId} contains an unresolved parameter`);
    }
    return route;
}

繁榮。現在,如果我傳遞了錯誤的參數,TypeScript 就會向我尖叫! 🎯

我還重構了AppLink來整合這個新的驗證:

const AppLink = <RouteId extends keyof RouteById>({
    routeId,
    ...props
}: RouteBuilderProps<RouteId> & Omit<LinkProps, "to"> & React.RefAttributes<HTMLAnchorElement>) => {
    return <Link to={buildRoute(routeId, props.params)} {...props}/>;
};
export default AppLink;

我進行了測試。一切正常。

我又開了另一個 PR。這次,喬確實喜歡它了。

“很好,我喜歡這個。確保路線真正是類型安全的。幹得好。”

😭甜蜜的驗證。

現在,不用再這樣寫:

<Link to="/projects/123/groups/456">View Group</Link>

我們可以寫:

<AppLink routeId="admin-project-group-consents" params={{ projectId: "123", groupId: "456" }}>
    View Group
</AppLink>

不再有錯誤的 URL。

TypeScript 強制驗證。

自動完成路線名稱和參數。

將來改變路徑時可以減少麻煩。

就這樣,路由系統終於變得堅不可摧了。 🔥


教訓

那天,我明白了:經驗確實很重要。

我可能會懷恨在心。我可能已經感到痛苦了。但相反,我學習了,提高了,並累積了知識

事實是,經驗不僅僅在於編碼所花的時間。這取決於你挑戰自己的深度、你的技能提升程度以及你對工具的真正了解程度。

那次公關對我來說是一個巨大的教訓。這不僅涉及路由、TypeScript 或 Joe 是否是 TypeScript 精靈。這是關於成長的事情

因此,如果您發現自己被更好的解決方案所折服,請不要抗拒。研究它、在此基礎上建構它、並使之成為你自己的。

這才是真正偉大的開發人員的素質。 😎

結束。


原文出處:https://dev.to/hichemtab-tech/the-day-i-truly-realized-i-was-a-junior-developer-kgp


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!