2026年2月27日 星期五

使用 OpenAI Codex 與透過自然語言來動手實作進行開發出一個 FHIR 為基礎的病人時序圖應用系統

使用 OpenAI Codex 與透過自然語言來動手實作進行開發出一個 FHIR 為基礎的病人時序圖應用系統

3分27秒代表甚麼意思?開發出來一個系統僅需要 3分27秒?這是什麼樣的魔法?先說我的感想,對於我而言,世界末日已經到了,不是正在來臨,我現在變成了 龜苓膏,我用我的 30年的歲月所建立起來的自我技能、技術、能力,經驗等等,在今天一天已經完全歸零了,我真的不知道未來如何來應對這個新的世界了,不過,再過兩年,我也60歲了,或許也不需要再去適應這個新的世界了,反正我也不會有什麼損失了,哈哈哈。

這裡是花了兩個星期做出的系統

這裡是花了3分27秒做出的系統

在 2026 年一開年,整個 Vibe Coding 應用造成許多的震撼,其中莫過於在 2026 年之後,AI將會接管許多之前必須要透過人才能夠開發出來的工作,而且做得更好,更快,甚至更便宜。這個趨勢在軟體開發領域造成的震撼是前所未有,而且是超乎大家的想像,這不是危言聳聽,軟體開發領域的天要塌下來了。面對一些重複性高、規則明確的工作,AI 可以幫助我們快速地完成這些工作,並且讓我們有更多的時間去思考和創造。

在 2025 一整年,我一直忙於公司的專案開發與雜事工作,說實在的,我沒有太多的時間去學習和使用 Vibe Coding 這類工具,雖然我知道它的威力和潛力,但我一直沒有機會去真正地體驗它的強大。這個比喻一點都不誇張,我在家裡半年不出門,今年一出門,卻不知道如何過生活,因為,以往我孰悉、精通的做事方式、生活態度、知識認知等等,發生了巨大變化,我突然變成了 龜苓膏 (是的,我真的歸零了),我以前自豪的技能、技術、能力,經驗等等,現在都變得毫無用處了,我需要重新學習和適應這個新的世界,這是一個非常大的挑戰,但也是一個非常有趣的冒險。

在這個 228 連續三天假期中,我想要來嘗試一下使用三種 Vibe Coding 技術,分別是 OpenAI Codex,Claude Code和 Gemini CLI 這三種工具,我想要用它們來進行開發出我之前一行一行慢慢做出來的以 FHIR 為基礎的病人時序圖應用系統,雖然之前手作的版本已經完成了,使用起來相當的有溫度且體驗感也不錯,因為當初設定想要在 2 個星期內快速開發出來,讓身旁的人可以知道 FHIR 為基礎的應用系統,可以做出甚麼樣的變化,因此,我做了關於 Patient、Encounter、Condition、Observation 這四類的資訊在病人時序圖中,當然,當初是期望能夠顯示更多的資訊,例如 AllergyIntolerance、Immunization、Device、Procedure、DiagnosticReport、DocumentReference、MedicationRequest 等等,但由於時間的限制,最後只能先做出這四類資訊的顯示,當然,這些資訊的顯示也不是很完整,還有很多細節沒有做出來,例如就診的醫療機構、就診的醫療人員等等,這些都是我想要在未來的版本中再慢慢補齊的。

今天,對於我從來沒有用過 Vibe Coding 的相關技術,從一開始到完成,累計花了約4個小時,邊學邊做,就完成了我之前與同事共同協作花了約兩個星期才完成的以 FHIR 為基礎的病人時序圖應用系統,這是一個非常驚人的成就,因為它展示了 Vibe Coding 技術的強大威力和潛力,讓我獨自一人就能夠在短時間內完成一個功能完整、易於使用的病人時序圖應用系統,這是我之前完全無法想像的。

這次,我先採用 OpenAI 推出的 Codex 來進行開發這個病人時序圖系統,之後,會再來體驗採用 Claude Code 和 Gemini CLI 這兩種工具來進行開發,看看它們的表現如何,是否能夠達到和 OpenAI Codex 一樣的效果,或者甚至更好。無論如何,我相信這些工具都會給我們帶來非常驚人的體驗,讓我們能夠在軟體開發領域中有更多的創造力和效率。

採用 OpenAI Codex 來開發一個 FHIR 為基礎的病人時序圖應用系統,這是一個非常有趣和有挑戰性的專案,因為它需要我們從 FHIR 的資料中提取出病人的就診紀錄,並且將這些紀錄以時序圖的形式展示出來。這個專案的目標是讓我們能夠快速地開發出一個功能完整、易於使用的病人時序圖應用系統,並且讓我們能夠體驗到 OpenAI Codex 在軟體開發領域的強大威力和潛力。

直到最近,我終於有機會去使用 OpenAI Codex 來開發一個 FHIR 為基礎的病人時序圖應用系統,這是一個非常有趣和有挑戰性的專案,面對一切未知與不清楚如何著手來做,我也不知道是否完成這樣的挑戰,不過,若真的要由以往的知識、經驗、技術來開發出來病人時序圖的系統,那麼,這個專案的開發過程將會非常的漫長和艱難,因為它需要我們從 FHIR 的資料中提取出病人的就診紀錄,並且將這些紀錄以時序圖的形式展示出來,這是一個非常複雜和繁瑣的工作,需要我們有非常深入的 FHIR 知識和經驗,以及非常熟悉 React 和 TypeScript 的技能,才能夠完成這樣的專案。

事前準備

在前一周,我下載的 Synthea™ 產生出來的 10 個模擬 FHIR 病人就醫紀錄,並且將這些紀錄匯入到網路上的免費 FHIR Server 上 ( https://server.fire.ly ),這些模擬病人就醫紀錄資料將會包含這些 FHIR 資源,包括 Organization、Location、Practitioner、PractitionerRole、Patient、Encounter、Condition、AllergyIntolerance、Immunization、Device、Observation、Procedure、DiagnosticReport、DocumentReference 以及 MedicationRequest 等等,這些資料將會成為我們開發病人時序圖應用系統的測試資料,讓我們能夠在開發過程中有實際的資料來進行測試和驗證。

使用 OpenAI Codex 來進行病人時序圖開發

  • 由於我毫無 OpenAI Codex 的經驗與知識,因此,我採用的是網頁版的 https://openai.com/zh-Hant/codex/ 這個平台來進行開發,因為它提供了一個非常簡單和易於使用的界面,讓我們能夠快速地開始使用 OpenAI Codex 來進行開發,並且它還提供了許多範例和教學資源,讓我們能夠更好地理解和使用 OpenAI Codex 的功能和特性。
  • 使用網頁開啟 OpenAI Codex 的平台後,並且登入到平台上
  • 我在 Github 網頁上建立一個新的 Repository PatientTimeline
  • 在 OpenAI Codex 網頁上,設定與綁定此次要做的後續工作,將會在這個 Repository 內來呈現
  • 在 OpenAI Codex 網頁上,介面相當的簡單,如同 ChatGPT 一樣,就只有一個文字輸入框
  • 然後在這個文字輸入框裡面,我就可以輸入我想要 OpenAI Codex 來幫我完成的工作內容,這些工作內容可以是一些具體的指令,例如「建立一個 React / TypeScript 的前端應用程式」,或者是一些更抽象的描述,例如「開發一個 FHIR 為基礎的病人時序圖應用系統」,無論是什麼樣的內容,我都可以直接在這個文字輸入框裡面輸入,然後 OpenAI Codex 就會根據我的輸入來生成相應的程式碼和文件,讓我能夠快速地完成我的專案開發。
  • 我將底下的內容輸入到這個文字輸入框裡面,讓 OpenAI Codex 來幫我完成這個專案的開發,這些內容包括了專案的目標、需求、開發工具與環境設定,以及每一個步驟的詳細說明,讓我能夠順利地使用 OpenAI Codex 來開發這個病人時序圖的前端應用程式。
我從 Synthea™ Patient Generator (GitHub) 下載了 Synthea™ 生成的 FHIR 資料,並使用 HAPI FHIR CLI 工具將資料匯入到 Firely Server 上 ( https://server.fire.ly )。以下是匯入的 FHIR Resource :
   * Organization : 模擬的醫療機構資料。
   * Location : 模擬的醫療機構地點資料。
   * Practitioner : 模擬的醫療人員資料。
   * PractitionerRole : 模擬的醫療人員角色資料。
   * Patient : 模擬的病人資料。
   * Encounter : 模擬的就診事件資料。
   * Condition : 模擬的疾病或健康狀態資料。
   * AllergyIntolerance : 模擬的過敏或不耐受資料。
   * Immunization : 模擬的疫苗接種資料。
   * Device : 模擬的醫療設備資料。
   * Observation : 模擬的觀察資料。
   * Procedure : 模擬的醫療程序資料。
   * DiagnosticReport : 模擬的診斷報告資料。
   * DocumentReference : 模擬的文件參考資料。
   * MedicationRequest : 模擬的用藥請求資料。

我需要一份使用 OpenAI Codex 來進行詳細操作指引規劃文件,可以參考 https://developers.openai.com/codex 官方文件的操作與建議說明,在這裡需要開發一個採用 React / TypeScript 的前端應用程式,該系統是病人時序圖,用於展示病人從就診到治療的整個過程。在這裡可以輸入病人 ID,如 129c6ac7-8d06-89de-ad63-0204a93e76c3,然後從 Firely Server 上獲取該病人的相關 FHIR 資料,並將這些資料以時序圖的形式展示出來。例如,這個病人可以從這個 URL 取得 https://server.fire.ly/Patient/129c6ac7-8d06-89de-ad63-0204a93e76c3

從病人時序圖畫面,可以看到指定區間內該病人的所有就醫紀錄與該病人的綜合摘要說明,並且可以點擊每一筆就醫紀錄來查看該就醫紀錄的詳細資訊。這些就醫紀錄可以包括病人就診的日期、就診的醫療機構、就診的醫療人員、就診的疾病或健康狀態、就診的過敏或不耐受、就診的疫苗接種、就診的醫療設備、就診的觀察、就診的醫療程序、就診的診斷報告、就診的文件參考以及就診的用藥請求等資訊。

由於我沒有使用過 OpenAI Codex 工具的經驗,需要列出如何安裝那些工具與軟體或檔案,輸入那些指令,在哪裡做甚麼操作, Prompt 該輸入甚麼內容,每一個步驟的詳細說明,讓我可以順利地使用 OpenAI Codex 來開發這個病人時序圖的前端應用程式。開發工具將會採用 VS Code,並且已經安裝 https://marketplace.visualstudio.com/items?itemName=openai.chatgpt 這個 VS Code 擴充套件來使用 OpenAI Codex 進行 開發與完成此專案。

最後,產生一個 markdown 檔案,告訴 OpenAI Codex,這個專案的目標、需求、開發工具與環境設定,以及每一個步驟的詳細說明,讓我可以順利地使用 OpenAI Codex 來開發這個病人時序圖的前端應用程式。
  • OpenAI Codex 會根據我輸入的內容來生成相應的程式碼和文件,開始進行相關工作
  • 接著,就只看到 OpenAI Codex 正在進行開發的訊息
  • 花了約 3分27秒,整個開發工作就完成了,底下就是 OpenAI Codex 的處理過程摘要
    • Summary
    • 建立了完整的 Vite + React + TypeScript 專案骨架,包含 scripts、依賴宣告、TypeScript/Vite/ESLint 設定,讓專案可作為前端 MVP 起點。.
    • 新增 FHIR 型別與 client(VITE_FHIR_BASE_URL、fetch、FHIR search helper),可直接對 Firely Server 進行 Patient/Bundle 查詢。.
    • 實作 React Query 的資料整合 hook,一次拉取 Patient、Encounter 與多種臨床資源(Condition/Observation/Procedure/DiagnosticReport/DocumentReference/MedicationRequest/AllergyIntolerance/Immunization/Device),並輸出 timeline 所需資料。.
    • 完成 timeline 映射邏輯:把多種 FHIR resource 正規化成 TimelineItem,支援 encounter reference 連結與 patient-level fallback 分組。.
    • 完成 MVP UI:Patient ID + 日期區間查詢、病人摘要、類型過濾、以 Encounter 分組事件清單,以及右側細節面板(含 raw JSON)。.
    • 補上 README 操作說明,並已提交 commit:40fb003;另外已建立 PR 訊息(依要求呼叫 make_pr)。.
    • Testing
    • ⚠️ npm install(環境對 npm registry 回傳 403,無法下載依賴)
    • ⚠️ npm run build(因依賴安裝受限,react/vite 等模組缺失導致建置失敗)
    • ⚠️ Playwright screenshot 嘗試連線 http://127.0.0.1:5173(因無法啟動 dev server,瀏覽器連線 ERR_EMPTY_RESPONSE)
  • 整個過程將會產生出來約 20 個程式碼檔案,包含了專案的骨架、前端的 React 組件、FHIR client 的實作、資料整合的 hook,以及 README 的操作說明等等,這些程式碼檔案將會構成整個病人時序圖應用系統的前端部分,讓我們能夠快速地開發出一個功能完整、易於使用的病人時序圖應用系統,並且讓我們能夠體驗到 OpenAI Codex 在軟體開發領域的強大威力和潛力。
  • 產生出來的程式已經發出一個 Pull Request,等待後續的 review 與 merge,這個 Pull Request 包含了整個病人時序圖應用系統的前端部分的程式碼
  • 我當然先不做做任何檢查,先直接接受這個 PR,然後直接 merge 到 main 分支,這樣就完成了整個專案的開發過程
  • 透過專案內的 README 操作說明,來啟動這個病人時序圖應用系統,並且進行測試,看看是否能夠成功地從 Firely Server 上獲取到指定病人的相關 FHIR 資料,並且將這些資料以時序圖的形式展示出來,看看整個系統的功能是否正常運作,以及使用體驗是否良好。
npm install
cp .env.example .env.local
npm run dev
  • 執行後,將會看到底下的內容
 VITE v6.4.1  ready in 298 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
  • 打開瀏覽器,輸入 http://localhost:5173/ 來訪問這個病人時序圖應用系統,看看是否能夠成功地從 Firely Server 上獲取到指定病人的相關 FHIR 資料,並且將這些資料以時序圖的形式展示出來,看看整個系統的功能是否正常運作,以及使用體驗是否良好。
  • 由於病歷號已經預設在畫面上了,這裡將僅輸入開始與結束時間
  • 點擊 [Load] 按鈕之後,將會出現這個畫面,表示正在載入資料中 
  • 等待資料載入完成之後,將會看到這個畫面,表示已經成功地從 Firely Server 上獲取到指定病人的相關 FHIR 資料,並且將這些資料以時序圖的形式展示出來,看看整個系統的功能是否正常運作,以及使用體驗是否良好。 

 




2026年2月23日 星期一

Blazor 元件生命週期事件深入探討 2 - 使用基本型別與類別引數的差異

Blazor 元件生命週期事件深入探討 2 - 使用基本型別與類別引數的差異

在上一篇 ASP.NET Core Blazor 元件生命週期事件深入探討 文章中,已經說明了 Blazor 元件的各種生命週期事件,以及在不同的情況下,哪些事件會被觸發,在這篇文章中,將會繼續深入探討 Blazor 元件的生命週期事件,並且透過一個範例專案來觀察在使用基本型別參數與類別型別參數時,Blazor 元件的生命週期事件的觸發情況,以及在不同的情況下,哪些事件會被觸發。

不過,當時使用的是 [Primitive Type] 資料型別,在這篇文章中,將會自行建立一個 [Address] 類別,並且在 Home 元件內宣告一個 Address 型別的參數,然後將這個參數傳遞給 Child 元件,這樣就可以觀察在使用類別型別參數時,Blazor 元件的生命週期事件的觸發情況,以及在不同的情況下,哪些事件會被觸發。

修正 Home.razor 元件的網頁標記

  • 打開 Home.razor 元件,並將以下程式碼複製貼上到該元件中:
  • 找到 <ChildView Name="@Name" Age="@Age" />
  • 將其改寫成 <ChildView Name="@Name" Age="@Age" Address="@Address" />,這裡將會新增一個 Address 參數,並且將 Home 元件內的 Address 參數傳遞給 ChildView 元件

修正 ChildView.razor.cs 元件的 Code-behind 程式碼

  • 打開 ChildView.razor.cs 元件,並將以下程式碼
  • 找到 public Address Address { get; set; } = new();
  • 將其改寫成 [Parameter] public Address Address { get; set; } = new();,這裡將會新增一個 Address 參數,並且將 Home 元件內的 Address 參數傳遞給 ChildView 元件

執行程式

首先先來看這個專案的執行結果:

  • 按下 F5 鍵或點擊[開始]按鈕來執行程式
  • 底下為在 [Console] 視窗內看到的資訊
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7019
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5121
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Vulcan\Github\CSharp2025\csBlazorLifeCycleEvent\csBlazorLifeCycleEvent
H1 - Home SetParametersAsync: 2026/2/23 下午 01:08:27 , {}
H2 - Home OnInitialized: 2026/2/23 下午 01:08:27
H3 - Home OnInitializedAsync: 2026/2/23 下午 01:08:27
H4 - Home OnParametersSet: 2026/2/23 下午 01:08:27
H5 - Home OnParametersSetAsync: 2026/2/23 下午 01:08:27
C1 - Child SetParametersAsync: 2026/2/23 下午 01:08:27 , {"Name":"Vulcan","Age":25,"Address":{"Country":"","City":""}}
     收到新的 Name: Vulcan
     收到新的 Age: 25
C2 - Child OnInitialized: 2026/2/23 下午 01:08:27
C3 - Child OnInitializedAsync: 2026/2/23 下午 01:08:27
C4 - Child OnParametersSet: 2026/2/23 下午 01:08:27
C5 - Child OnParametersSetAsync: 2026/2/23 下午 01:08:27
H6 - Home OnAfterRender: 2026/2/23 下午 01:08:27, firstRender: True
H7 - Home OnAfterRenderAsync: 2026/2/23 下午 01:08:27, firstRender: True
C6 - Child OnAfterRender: 2026/2/23 下午 01:08:27, firstRender: True
C7 - Child OnAfterRenderAsync: 2026/2/23 下午 01:08:27, firstRender: True
  • 這裡看到的結果,與上一篇文章看到的差異在於,在 ChildView 元件的 [SetParametersAsync] 事件內,接收到的參數值中,除了 Name 和 Age 這兩個基本型別參數之外,還多了一個 Address 這個類別型別參數,並且在該事件內,接收到的 Address 參數值為一個新的 Address 物件,該物件的 Country 和 City 屬性值都是空字串,這是因為在 Home 元件內宣告的 Address 參數初始值為一個新的 Address 物件,而該物件的 Country 和 City 屬性值都是空字串,所以在 ChildView 元件內接收到的 Address 參數值也是一個新的 Address 物件,而該物件的 Country 和 City 屬性值也是空字串。
  • 接下來,將會在 Home 元件內,點擊 [僅觸發按鈕事件] 按鈕,這個按鈕所綁定的事件,並沒有做任何事情,觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:
H8 - Home ShouldRender: 2026/2/23 下午 01:12:28
C1 - Child SetParametersAsync: 2026/2/23 下午 01:12:28 , {"Name":"Vulcan","Age":25,"Address":{"Country":"","City":""}}
C4 - Child OnParametersSet: 2026/2/23 下午 01:12:28
C5 - Child OnParametersSetAsync: 2026/2/23 下午 01:12:28
C8 - Child ShouldRender: 2026/2/23 下午 01:12:28
H6 - Home OnAfterRender: 2026/2/23 下午 01:12:28, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 下午 01:12:28, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 下午 01:12:28, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 下午 01:12:28, firstRender: False
  • 底下的內容將會是上一篇文章中看到的執行結果
H8 - Home ShouldRender: 2026/2/23 上午 10:48:17
H6 - Home OnAfterRender: 2026/2/23 上午 10:48:17, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 10:48:17, firstRender: False
  • 比較這兩篇文章的執行結果,在這篇中,有使用一個自訂的 [Address] 類別型別參數,並且將其傳遞給 ChildView 元件,而在上一篇文章中,沒有使用這個類別型別參數,所以在這篇文章中,在 Home 元件內點擊 [僅觸發按鈕事件] 按鈕之後,ChildView 元件的 [SetParametersAsync] 事件會被觸發,並且在該事件內接收到的參數值中,包含了 Name、Age 和 Address 這三個參數值,而在上一篇文章中,在 Home 元件內點擊 [僅觸發按鈕事件] 按鈕之後,ChildView 元件的 [SetParametersAsync] 事件不會被觸發,因為沒有任何參數值被傳遞給 ChildView 元件,所以在該事件內也不會接收到任何參數值,這就是使用基本型別參數與類別型別參數的差異,在使用基本型別參數時,如果沒有改變參數值,則不會觸發子元件的生命週期事件,而在使用類別型別參數時,即使沒有改變參數值,由於傳遞的是物件的參考,所以仍然會觸發子元件的生命週期事件,並且在該事件內接收到的參數值仍然是同一個物件的參考,這就是使用基本型別參數與類別型別參數的差異,這也是在 Blazor 開發中需要注意的一點,當使用類別型別參數時,即使沒有改變參數值,也會觸發子元件的生命週期事件,並且在該事件內接收到的參數值仍然是同一個物件的參考,所以在撰寫 Blazor 元件時,需要注意這一點,以避免不必要的事件觸發和效能問題。
  • 接下來,將會在 Home 元件內,點擊 [觸發按鈕事件並變更 1 個參數] 按鈕,這個按鈕所綁定的事件,會將 Razor 元件的 Name 參數變更為 "Spock",觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:
H8 - Home ShouldRender: 2026/2/23 下午 01:16:28
C1 - Child SetParametersAsync: 2026/2/23 下午 01:16:28 , {"Name":"Spock","Age":25,"Address":{"Country":"","City":""}}
     收到新的 Name: Spock
C4 - Child OnParametersSet: 2026/2/23 下午 01:16:28
C5 - Child OnParametersSetAsync: 2026/2/23 下午 01:16:28
C8 - Child ShouldRender: 2026/2/23 下午 01:16:28
H6 - Home OnAfterRender: 2026/2/23 下午 01:16:28, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 下午 01:16:28, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 下午 01:16:28, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 下午 01:16:28, firstRender: False
  • 底下是上一篇文章的同一個按鈕的執行效果
H8 - Home ShouldRender: 2026/2/23 上午 11:32:05
C1 - Child SetParametersAsync: 2026/2/23 上午 11:32:05 , {"Name":"Spock","Age":25}
     收到新的 Name: Spock
C4 - Child OnParametersSet: 2026/2/23 上午 11:32:05
C5 - Child OnParametersSetAsync: 2026/2/23 上午 11:32:05
C8 - Child ShouldRender: 2026/2/23 上午 11:32:05
H6 - Home OnAfterRender: 2026/2/23 上午 11:32:05, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 11:32:05, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 上午 11:32:05, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 上午 11:32:05, firstRender: False
  • 比較這兩篇文章的執行結果,差異在於,在這篇文章中,在 Home 元件內點擊 [觸發按鈕事件並變更 1 個參數] 按鈕之後,ChildView 元件的 [SetParametersAsync] 事件會被觸發,並且在該事件內接收到的參數值中,包含了 Name、Age 和 Address 這三個參數值,而在上一篇文章中,在 Home 元件內點擊 [觸發按鈕事件並變更 1 個參數] 按鈕之後,ChildView 元件的 [SetParametersAsync] 事件同樣會被觸發,並且在該事件內接收到的參數值中,包含了 Name 和 Age 這兩個參數值,但沒有 Address 這個類別型別參數值,這就是使用基本型別參數與類別型別參數的差異,在使用基本型別參數時,如果沒有改變參數值,則不會觸發子元件的生命週期事件,而在使用類別型別參數時,即使沒有改變參數值,由於傳遞的是物件的參考,所以仍然會觸發子元件的生命週期事件,並且在該事件內接收到的參數值仍然是同一個物件的參考,這就是使用基本型別參數與類別型別參數的差異,這也是在 Blazor 開發中需要注意的一點,當使用類別型別參數時,即使沒有改變參數值,也會觸發子元件的生命週期事件,並且在該事件內接收到的參數值仍然是同一個物件的參考,所以在撰寫 Blazor 元件時,需要注意這一點,以避免不必要的事件觸發和效能問題。
  • 接下來,將會在 Home 元件內,點擊 [觸發按鈕事件並變更參數的物件屬性值] 按鈕,這個按鈕所綁定的事件,會將 Razor 元件的 Address 參數的 Country 屬性值變更為 "USA",觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:
H8 - Home ShouldRender: 2026/2/23 下午 01:19:38
C1 - Child SetParametersAsync: 2026/2/23 下午 01:19:38 , {"Name":"Spock","Age":25,"Address":{"Country":"USA","City":"New York"}}
C4 - Child OnParametersSet: 2026/2/23 下午 01:19:38
C5 - Child OnParametersSetAsync: 2026/2/23 下午 01:19:38
C8 - Child ShouldRender: 2026/2/23 下午 01:19:38
H6 - Home OnAfterRender: 2026/2/23 下午 01:19:38, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 下午 01:19:38, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 下午 01:19:38, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 下午 01:19:38, firstRender: False
  • 這裡看到的執行結果為,在 Home 元件內點擊 [觸發按鈕事件並變更參數的物件屬性值] 按鈕之後,ChildView 元件的 [SetParametersAsync] 事件會被觸發,並且在該事件內接收到的參數值中,包含了 Name、Age 和 Address 這三個參數值,而在該事件內接收到的 Address 參數值中,Country 屬性值已經變更為 "USA",City 屬性值仍然是 "New York",這是因為在 Home 元件內宣告的 Address 參數初始值為一個新的 Address 物件,而該物件的 Country 和 City 屬性值都是空字串,所以在 ChildView 元件內接收到的 Address 參數值也是一個新的 Address 物件,而該物件的 Country 和 City 屬性值也是空字串,但當點擊 [觸發按鈕事件並變更參數的物件屬性值] 按鈕之後,Home 元件內宣告的 Address 參數所參考的物件的 Country 屬性值被改變為 "USA",所以在 ChildView 元件內接收到的 Address 參數值中,Country 屬性值也被改變為 "USA",而 City 屬性值仍然是 "New York",這就是使用類別型別參數時,即使沒有改變參數值,由於傳遞的是物件的參考,所以仍然會觸發子元件的生命週期事件,並且在該事件內接收到的參數值仍然是同一個物件的參考,所以當改變了該物件的屬性值之後,在子元件內接收到的該物件的屬性值也會被改變,這就是使用類別型別參數時需要注意的一點,以避免不必要的事件觸發和效能問題。

 




ASP.NET Core Blazor 元件生命週期事件深入探討

ASP.NET Core Blazor 元件生命週期事件深入探討

在 Blazor 開發框架內,Razor 元件的生命週期事件是非常重要的一部分,這些事件會在 Razor 元件的不同階段被觸發,開發者可以在這些事件內撰寫程式碼來實現特定的邏輯,例如在元件初始化時載入資料,在參數變更時更新畫面等。

如何充分掌握與理解這些 Razor 元件的生命週期運作方式與原理,將會有助於寫出更加高效率與好用的 Blazor 網頁系統,因此,在這篇文章中,將會深入探討 Blazor 元件的生命週期事件,並且透過一個範例專案來觀察這些事件的觸發情況,以及在不同的情況下,哪些事件會被觸發。

在這裡將會新設計一個 [Child] 元件,並且在父元件 [Home] 中來參考這個子元件,在這個範例專案中,將會在 Home 元件內宣告一些參數,並且將這些參數傳遞給 Child 元件,然後在 Home 元件和 Child 元件內複寫各種生命週期事件的方法,並且在這些方法內輸出一些訊息,以便在後續的文章中觀察這些事件方法的觸發情況,以及觸發的順序和時間點,這些訊息將有助於理解 Blazor 元件的生命週期事件是如何運作的,以及在不同的情況下,哪些事件會被觸發。

建立 Blazor 專案

  • 開啟 Visual Studio 2026
  • 選擇[建立新專案]
  • 在 [建立新專案] 視窗中,在右方清單內,找到並選擇[Blazor Web 應用程式] 項目
  • 然後點擊右下方[下一步]按鈕
  • 此時將會看到 [設定新的專案] 對話窗
  • 在該對話窗的 [專案名稱] 欄位中,輸入專案名稱,例如 "csBlazorLifeCycleEvent"
  • 然後點擊右下方[下一步]按鈕
  • 接著會看到 [其他資訊] 對話窗
  • 在這個對話窗內,確認使用底下的選項
    • 架構:.NET 10.0 (或更新版本)
    • 驗證類型:無
    • 勾選 針對 HTTPS 進行設定
    • 互動式轉譯模式:伺服器
    • 互動功能位置:全球
    • 勾選 包和範例頁面
    • 勾選 不要使用最上層陳述式 (這是我的個人習慣)
    • 不要勾選 在應用程式 URL 中使用 .dev.localhost TLD
    • 不要勾選 在 .NET Aspire 協調流程中登錄
  • 然後點擊右下方[建立]按鈕
  • 現在,已經完成了這個 Blazor 專案的建立

建立 Home 元件的 Code-behind 程式碼

  • 在專案根目錄下,滑鼠右擊 [Components] > [Pages] 資料夾
  • 從右鍵選單中,選擇[加入]>[新增項目]
  • 這裡將會使用 [顯示精簡檢視] 的方式來輸入要新增的項目
  • 在 [新增項目] 對話窗內的文字輸入盒內,輸入 [Home.razor.cs]
  • 然後點擊[加入]按鈕
  • 現在,已經成功建立了 Home.razor.cs 類別檔案
  • 接著,打開 Home.razor.cs 類別檔案,並將以下程式碼複製貼上到該檔案中:
using Microsoft.AspNetCore.Components;

namespace csBlazorLifeCycleEvent.Components.Pages;

public partial class Home
{
    public string Name { get; set; } = "Vulcan";

    public int Age { get; set; } = 25;

    public Address Address { get; set; } = new Address();

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var dict = parameters.ToDictionary();
        var json = System.Text.Json.JsonSerializer.Serialize(dict);
        Console.WriteLine($"H1 - Home SetParametersAsync: {DateTime.Now} , {json}");
        await base.SetParametersAsync(parameters);
    }
    override protected void OnInitialized()
    {
        Console.WriteLine($"H2 - Home OnInitialized: {DateTime.Now}");
    }
    override protected async Task OnInitializedAsync()
    {
        Console.WriteLine($"H3 - Home OnInitializedAsync: {DateTime.Now}");
    }
    override protected void OnParametersSet()
    {
        Console.WriteLine($"H4 - Home OnParametersSet: {DateTime.Now}");
    }
    override protected async Task OnParametersSetAsync()
    {
        Console.WriteLine($"H5 - Home OnParametersSetAsync: {DateTime.Now}");
    }
    override protected void OnAfterRender(bool firstRender)
    {
        Console.WriteLine($"H6 - Home OnAfterRender: {DateTime.Now}, firstRender: {firstRender}");
    }
    override protected async Task OnAfterRenderAsync(bool firstRender)
    {
        Console.WriteLine($"H7 - Home OnAfterRenderAsync: {DateTime.Now}, firstRender: {firstRender}");
    }
    protected override bool ShouldRender()
    {
        Console.WriteLine($"H8 - Home ShouldRender: {DateTime.Now}");
        return base.ShouldRender();
    }
    void OnButtonNothingClick()
    {
        Console.WriteLine();
        Console.WriteLine();
    }
    void OnButtonChange1ParameterClick()
    {
        Console.WriteLine();
        Console.WriteLine();
        Name = "Spock";
    }
    void OnButtonChange2ParameterClick()
    {
        Console.WriteLine();
        Console.WriteLine();
        Name = "Tom";
        Age = 35;
    }
    void OnButtonChangeObjectPropertyParameterClick()
    {
        Console.WriteLine();
        Console.WriteLine();
        Address.Country = "USA";
        Address.City = "New York";
    }
}
  • 在這個 Code-behind 程式碼中,宣告了三個屬性,分別是 Name、Age 和 Address,在這篇文章中,將僅會把前兩個屬性傳遞給子元件 ChildView,Address 屬性將不會被傳遞給子元件 ChildView(這是因為前兩個屬性為基本資料類別型,而 Address 屬性為複雜資料類型,這樣做的目的是為了在後續的文章中,觀察在父元件 Home 的參數變更時,子元件 ChildView 的生命週期事件觸發情況)
  • 接下來將會複寫 Razor 元件內的各種生命週期的事件方法,並在這些方法內輸出一些訊息,以便在後續的文章中觀察這些事件方法的觸發情況
  • 第一個事件是 [SetParametersAsync] 這個事件會在 Razor 元件接收到參數時被觸發,無論是父元件傳遞的參數變更,還是使用者互動導致的參數變更,都會觸發這個事件,在這裡會將收到 [ParameterView] 物件轉換成字典,並序列化成 JSON 字串,然後輸出到控制台,以便觀察接收到的參數內容
  • 第二個事件是 [OnInitialized] 這個事件會在 Razor 元件被初始化時被觸發,這個事件只會觸發一次,在這裡會輸出一條訊息到控制台,以便觀察這個事件的觸發情況
  • 第三個事件是 [OnInitializedAsync] 這個事件也是在 Razor 元件被初始化時被觸發,但這個事件是非同步的,這個事件也只會觸發一次,在這裡同樣會輸出一條訊息到控制台,以便觀察這個事件的觸發情況(對於 OnInitialized 和 OnInitializedAsync 這兩個事件,從輸出訊息中,將可以看出觸發順序,以及觸發的時間點)
  • 第四個事件是 [OnParametersSet] 這個事件會在 Razor 元件接收到參數變更時被觸發,無論是父元件傳遞的參數變更,還是使用者互動導致的參數變更,都會觸發這個事件,在這裡會輸出一條訊息到控制台,以便觀察這個事件的觸發情況,這個事件將不會接收到任何的參數內容,這個事件的觸發時機是在 SetParametersAsync 事件之後,並且在 Razor 元件重新渲染之前
  • 第五個事件是 [OnParametersSetAsync] 這個事件也是在 Razor 元件接收到參數變更時被觸發,但這個事件是非同步的,這個事件的觸發時機也是在 SetParametersAsync 事件之後,並且在 Razor 元件重新渲染之前,在這裡同樣會輸出一條訊息到控制台,以便觀察這個事件的觸發情況
  • 第六個事件是 [OnAfterRender] 這個事件會在 Razor 元件完成渲染後被觸發,這個事件會接收一個布林值參數 [firstRender],用來表示這是否是 Razor 元件第一次完成渲染,在這裡會輸出一條訊息到控制台,以便觀察這個事件的觸發情況,以及是否為第一次渲染
  • 第七個事件是 [OnAfterRenderAsync] 這個事件也是在 Razor 元件完成渲染後被觸發,但這個事件是非同步的,這個事件同樣會接收一個布林值參數 [firstRender],用來表示這是否是 Razor 元件第一次完成渲染,在這裡同樣會輸出一條訊息到控制台,以便觀察這個事件的觸發情況,以及是否為第一次渲染
  • 第八個事件是 [ShouldRender] 這個事件會在 Razor 元件接收到參數變更,或者內部狀態變更時被觸發,這個事件會決定 Razor 元件是否需要重新渲染,在這裡會輸出一條訊息到控制台,以便觀察這個事件的觸發情況,並且會回傳 base.ShouldRender() 的結果,這樣就會使用預設的邏輯來決定是否重新渲染 Razor 元件
  • 接下來,宣告了四個方法,分別是:
  • [OnButtonNothingClick] 將會當使用者按下這個按鈕後所觸發的委派方法,這個方法內不會對 Razor 元件的參數進行任何變更,這樣就可以觀察在使用者互動但沒有參數變更的情況下,Razor 元件的生命週期事件的觸發情況
  • [OnButtonChange1ParameterClick] 將會當使用者按下這個按鈕後所觸發的委派方法,這個方法內會將 Razor 元件的 Name 參數變更為 "Spock",這樣就可以觀察在使用者互動並且有一個參數變更的情況下,Razor 元件的生命週期事件的觸發情況
  • [OnButtonChange2ParameterClick] 將會當使用者按下這個按鈕後所觸發的委派方法,這個方法內會將 Razor 元件的 Name 參數變更為 "Tom",並且將 Age 參數變更為 35,這樣就可以觀察在使用者互動並且有多個參數變更的情況下,Razor 元件的生命週期事件的觸發情況
  • [OnButtonChangeObjectPropertyParameterClick] 將會當使用者按下這個按鈕後所觸發的委派方法,這個方法內會將 Razor 元件的 Address 參數內部物件的 Country 屬性變更為 "USA",並且將 City 屬性變更為 "New York",這樣就可以觀察在使用者互動並且有複雜資料類型參數內部屬性變更的情況下,Razor 元件的生命週期事件的觸發情況

修正 Home.razor 元件的網頁標記

  • 打開 Home.razor 元件,並將以下程式碼複製貼上到該元件中:
@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<div class="p-3">
    <button class="btn btn-primary" @onclick="OnButtonNothingClick">僅觸發按鈕事件</button>
    <button class="btn btn-secondary" @onclick="OnButtonChange1ParameterClick">觸發按鈕事件並變更 1 個參數</button>
    <button class="btn btn-success" @onclick="OnButtonChange2ParameterClick">觸發按鈕事件並變更 2 個參數</button>
    <button class="btn btn-success" @onclick="OnButtonChangeObjectPropertyParameterClick">觸發按鈕事件並變更參數的物件屬性值</button>
</div>

<div class="p-3">
    <div class="card">
        <div class="card-body">
            <p>This is the parent view.</p>
            <p>Name : @Name</p>
            <p>Age : @Age</p>
            <p>Address Hash : @Address.GetHashCode()</p>
            <p>Country : @Address.Country</p>
            <p>City : @Address.City</p>
        </div>
    </div>
</div>

<div class="p-3">
    <ChildView Name="@Name" Age="@Age" />
</div>
  • 從 <ChildView Name="@Name" Age="@Age" /> 這行程式碼中,可以看到將 Home 元件的 Name 和 Age 參數傳遞給了 ChildView 元件,而 Address 參數則沒有被傳遞給 ChildView 元件

建立 ChildView 元件

  • 在專案根目錄下,滑鼠右擊 [Components] > [Pages] 資料夾
  • 從右鍵選單中,選擇[加入]>[Razor 元件]
  • 在 [新增項目] 對話窗內的文字輸入盒內,輸入 [ChildView.razor]
  • 然後點擊[加入]按鈕
  • 現在,已經成功建立了 ChildView.razor 類別檔案
  • 接著,打開 ChildView.razor 類別檔案,並將以下程式碼複製貼上到該檔案中:

建立 ChildView 元件的 Code-behind 程式碼

  • 在專案根目錄下,滑鼠右擊 [Components] > [Pages] 資料夾
  • 從右鍵選單中,選擇[加入]>[新增項目]
  • 這裡將會使用 [顯示精簡檢視] 的方式來輸入要新增的項目
  • 在 [新增項目] 對話窗內的文字輸入盒內,輸入 [ChildView.razor.cs]
  • 然後點擊[加入]按鈕
  • 現在,已經成功建立了 ChildView.razor.cs 類別檔案
  • 接著,打開 ChildView.razor.cs 類別檔案,並將以下程式碼複製貼上到該檔案中:
using Microsoft.AspNetCore.Components;

namespace csBlazorLifeCycleEvent.Components.Pages;

public partial class ChildView
{
    [Parameter]
    public string Name { get; set; } = string.Empty;

    [Parameter]
    public int Age { get; set; }
    public Address Address { get; set; } = new();
    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var dict = parameters.ToDictionary();
        var json = System.Text.Json.JsonSerializer.Serialize(dict);
        Console.WriteLine($"C1 - Child SetParametersAsync: {DateTime.Now} , {json}");
        if (parameters.TryGetValue<string>("Name", out var newName))
        {
            if (Name != newName)
                Console.WriteLine($"     收到新的 Name: {newName}");
        }
        if (parameters.TryGetValue<int>("Age", out var newAge))
        {
            if (Age != newAge)
                Console.WriteLine($"     收到新的 Age: {newAge}");
        }
        await base.SetParametersAsync(parameters);
    }
    override protected void OnInitialized()
    {
        Console.WriteLine($"C2 - Child OnInitialized: {DateTime.Now}");
    }
    override protected async Task OnInitializedAsync()
    {
        Console.WriteLine($"C3 - Child OnInitializedAsync: {DateTime.Now}");
    }
    override protected void OnParametersSet()
    {
        Console.WriteLine($"C4 - Child OnParametersSet: {DateTime.Now}");
    }
    override protected async Task OnParametersSetAsync()
    {
        Console.WriteLine($"C5 - Child OnParametersSetAsync: {DateTime.Now}");
    }
    override protected void OnAfterRender(bool firstRender)
    {
        Console.WriteLine($"C6 - Child OnAfterRender: {DateTime.Now}, firstRender: {firstRender}");
    }
    override protected async Task OnAfterRenderAsync(bool firstRender)
    {
        Console.WriteLine($"C7 - Child OnAfterRenderAsync: {DateTime.Now}, firstRender: {firstRender}");
    }
    protected override bool ShouldRender()
    {
        Console.WriteLine($"C8 - Child ShouldRender: {DateTime.Now}");
        return base.ShouldRender();
    }
}
  • 在這個 Code-behind 程式碼中,宣告了兩個參數屬性,使用了 [Parameter],分別是 Name 和 Age,這兩個參數將會從父元件 Home 傳遞過來,在這裡同樣複寫了 Razor 元件內的各種生命週期的事件方法,並在這些方法內輸出一些訊息,以便在後續的文章中觀察這些事件方法的觸發情況,這些事件方法與 Home 元件內的事件方法相同,只是在輸出的訊息中,將 Home 改為 Child,以便區分是 Home 元件的事件觸發,還是 ChildView 元件的事件觸發

修正 ChildView.razor 元件的網頁標記

  • 打開 ChildView.razor 元件,並將以下程式碼複製貼上到該元件中:
<div class="card">
    <div class="card-body">
        <p>This is the child view.</p>
        <p>Name : @Name</p>
        <p>Age : @Age</p>
        <p>Address Hash : @Address.GetHashCode()</p>
        <p>Country : @Address.Country</p>
        <p>City : @Address.City</p>
    </div>
</div>

取消預先渲覽功能

  • 在專案根目錄下的[Components] 資料夾
  • 找到並且打開 [App.razor] 元件
  • 搜尋出程式碼 <HeadOutlet @rendermode="InteractiveServer" /> 將其改寫成為 <HeadOutlet @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />,以取消預先渲覽功能
  • 搜尋出程式碼 <Routes @rendermode="InteractiveServer" /> 將其改寫成為 <Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />,以取消預先渲覽功能

執行程式

首先先來看這個專案的執行結果:

  • 按下 F5 鍵或點擊[開始]按鈕來執行程式
  • 在瀏覽器中,將會看到 Home 元件的內容,以及 ChildView 元件的內容,如下圖 
  • 在最上方出現的四個按鈕
  • 接著出現的卡片內容,則是在 [Home] 這個元件內加入的標記內容
  • 然後在下方看到的卡片內容,則是在 [ChildView] 這個元件內加入的標記內容
  • 底下為在 [Console] 視窗內看到的資訊
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7019
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5121
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Vulcan\Github\CSharp2025\csBlazorLifeCycleEvent\csBlazorLifeCycleEvent
H1 - Home SetParametersAsync: 2026/2/23 上午 10:38:35 , {}
H2 - Home OnInitialized: 2026/2/23 上午 10:38:35
H3 - Home OnInitializedAsync: 2026/2/23 上午 10:38:35
H4 - Home OnParametersSet: 2026/2/23 上午 10:38:35
H5 - Home OnParametersSetAsync: 2026/2/23 上午 10:38:35
C1 - Child SetParametersAsync: 2026/2/23 上午 10:38:35 , {"Name":"Vulcan","Age":25}
     收到新的 Name: Vulcan
     收到新的 Age: 25
C2 - Child OnInitialized: 2026/2/23 上午 10:38:35
C3 - Child OnInitializedAsync: 2026/2/23 上午 10:38:35
C4 - Child OnParametersSet: 2026/2/23 上午 10:38:35
C5 - Child OnParametersSetAsync: 2026/2/23 上午 10:38:35
H6 - Home OnAfterRender: 2026/2/23 上午 10:38:35, firstRender: True
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 10:38:35, firstRender: True
C6 - Child OnAfterRender: 2026/2/23 上午 10:38:35, firstRender: True
C7 - Child OnAfterRenderAsync: 2026/2/23 上午 10:38:35, firstRender: True
  • 從這些輸出的訊息中,可以觀察到在 Home 元件和 ChildView 元件的各種生命週期事件的觸發情況,以及觸發的順序和時間點,這些訊息將有助於理解 Blazor 元件的生命週期事件是如何運作的,以及在不同的情況下,哪些事件會被觸發

  • 這裡將會說明這些事件觸發的順序和時間點,首先在 Home 元件中,當 Razor 元件被初始化時,會先觸發 [SetParametersAsync] 事件,然後觸發 [OnInitialized] 事件,接著觸發 [OnInitializedAsync] 事件,然後觸發 [OnParametersSet] 事件,最後觸發 [OnParametersSetAsync] 事件,在 ChildView 元件中,當 Razor 元件被初始化時,同樣會先觸發 [SetParametersAsync] 事件,然後觸發 [OnInitialized] 事件,接著觸發 [OnInitializedAsync] 事件,然後觸發 [OnParametersSet] 事件,最後觸發 [OnParametersSetAsync] 事件,在 Home 元件和 ChildView 元件的這些事件之後,才會觸發 [OnAfterRender] 和 [OnAfterRenderAsync] 這兩個事件,這些事件的觸發順序和時間點是固定的,無論是在哪個元件中,這些事件的觸發順序和時間點都是相同的,這些事件的觸發順序和時間點是由 Blazor 框架所定義的,開發者無法改變這些事件的觸發順序和時間點,這些事件的觸發順序和時間點是 Blazor 元件生命週期的一部分,理解這些事件的觸發順序和時間點,對於開發 Blazor 元件是非常重要的,這些事件的觸發順序和時間點,將會影響到元件的行為和性能,開發者需要根據這些事件的觸發順序和時間點,來設計元件的邏輯和行為,以達到最佳的性能和使用者體驗

  • 接下來,將會在 Home 元件內,點擊 [僅觸發按鈕事件] 按鈕,這個按鈕所綁定的事件,並沒有做任何事情,觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:

H8 - Home ShouldRender: 2026/2/23 上午 10:48:17
H6 - Home OnAfterRender: 2026/2/23 上午 10:48:17, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 10:48:17, firstRender: False
  • 由於該按鈕並沒有做任何事情,而是觸發了重新渲染 Home 元件的事件,所以在 Console 視窗內,將會看到 Home 元件的 [ShouldRender] 事件被觸發,然後是 [OnAfterRender] 和 [OnAfterRenderAsync] 這兩個事件被觸發,而由於傳遞到子元件內的參數並沒有改變,所以 ChildView 元件的生命週期事件不會被觸發

  • 接下來,將會在 Home 元件內,點擊 [觸發按鈕事件並變更 1 個參數] 按鈕,這個按鈕所綁定的事件,會將 Razor 元件的 Name 參數變更為 "Spock",觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:

H8 - Home ShouldRender: 2026/2/23 上午 11:32:05
C1 - Child SetParametersAsync: 2026/2/23 上午 11:32:05 , {"Name":"Spock","Age":25}
     收到新的 Name: Spock
C4 - Child OnParametersSet: 2026/2/23 上午 11:32:05
C5 - Child OnParametersSetAsync: 2026/2/23 上午 11:32:05
C8 - Child ShouldRender: 2026/2/23 上午 11:32:05
H6 - Home OnAfterRender: 2026/2/23 上午 11:32:05, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 11:32:05, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 上午 11:32:05, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 上午 11:32:05, firstRender: False
  • 這裡是網頁畫面截圖 
  • 由於該按鈕觸發了參數變更,所以在 Console 視窗內,將會看到 Home 元件的 [ShouldRender] 事件被觸發,然後 ChildView 元件的 [SetParametersAsync] 事件被觸發,並且在該事件內,接收到新的 Name 參數值為 "Spock",接著是 ChildView 元件的 [OnParametersSet] 和 [OnParametersSetAsync] 這兩個事件被觸發,然後是 ChildView 元件的 [ShouldRender] 事件被觸發,最後是 Home 元件和 ChildView 元件的 [OnAfterRender] 和 [OnAfterRenderAsync] 這四個事件被觸發,而由於傳遞到子元件內的參數有改變,所以 ChildView 元件的生命週期事件也會被觸發
  • 接下來,將會在 Home 元件內,點擊 [觸發按鈕事件並變更 2 個參數] 按鈕,這個按鈕所綁定的事件,會將 Razor 元件的 Name 參數變更為 "Tom",並且將 Age 參數變更為 35,觀察當這個按鈕被點擊之後,這些事件會如何被觸發,當點擊這個按鈕之後,將會在 Console 視窗內看到以下的輸出訊息:
H8 - Home ShouldRender: 2026/2/23 上午 11:34:33
C1 - Child SetParametersAsync: 2026/2/23 上午 11:34:33 , {"Name":"Tom","Age":35}
     收到新的 Name: Tom
     收到新的 Age: 35
C4 - Child OnParametersSet: 2026/2/23 上午 11:34:33
C5 - Child OnParametersSetAsync: 2026/2/23 上午 11:34:33
C8 - Child ShouldRender: 2026/2/23 上午 11:34:33
H6 - Home OnAfterRender: 2026/2/23 上午 11:34:33, firstRender: False
H7 - Home OnAfterRenderAsync: 2026/2/23 上午 11:34:33, firstRender: False
C6 - Child OnAfterRender: 2026/2/23 上午 11:34:33, firstRender: False
C7 - Child OnAfterRenderAsync: 2026/2/23 上午 11:34:33, firstRender: False
  • 這裡是網頁畫面截圖 

  • 由於該按鈕觸發了參數變更,所以在 Console 視窗內,將會看到 Home 元件的 [ShouldRender] 事件被觸發,然後 ChildView 元件的 [SetParametersAsync] 事件被觸發,並且在該事件內,接收到新的 Name 參數值為 "Tom",以及新的 Age 參數值為 35,接著是 ChildView 元件的 [OnParametersSet] 和 [OnParametersSetAsync] 這兩個事件被觸發,然後是 ChildView 元件的 [ShouldRender] 事件被觸發,最後是 Home 元件和 ChildView 元件的 [OnAfterRender] 和 [OnAfterRenderAsync] 這四個事件被觸發,而由於傳遞到子元件內的參數有改變,所以 ChildView 元件的生命週期事件也會被觸發