您好,登錄后才能下訂單哦!
這篇文章給大家介紹Flutter路由管理代碼這么長怎么高效解決,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
01 背景
在Flutter的業務開發過程中,Flutter側會逐漸豐富自己的路由管理。一個輕量的路由管理本質上是頁面標識(或頁面路徑)與頁面實例的映射。本文工程師將基于dart注解提供了一個輕量路由管理方案。
不論是在native與Flutter的混合工程,還是純Flutter開發的工程,當我們實現一個輕量路由的時候一般會有以下幾種方法:
1. 較差的實現,if-else的邏輯堆疊:
做映射時較差的實現是通過if-else的邏輯判斷把url映射到對應的widget實例上。
class Router { Widget route(String url, Map params) { if(url == 'myapp://apage') { return PageA(url); } else if(url == 'myapp://bpage') { return PageB(url, params); } } }
這樣做的弊端比較明顯:
1)每個映射的維護影響全局映射配置的穩定性,每次維護映射管理時需要腦補所有的邏輯分支。
2)無法做到頁面的統一抽象,頁面的構造器和構造邏輯被開發者自定義。
3)映射配置無法與頁面聯動,把頁面級的配置進行中心化的維護,導致維護責任人缺失。
2. 一般的實現,手動維護的映射表:
稍微好一點的是將映射關系通過一個配置信息和一個工廠方法來表現。
class Router { Map<String, dynamic> mypages = <String, dynamic> { 'myapp://apage': 'pagea', 'myapp://bpage': 'pageb' } Widget route(String url, Map params) { String pageId = mypages[url]; return getPageFromPageId(pageId); } Widget getPageFromPageId(String pageId) { switch(pageId) { case 'pagea': return PageA(); case 'pageb': return PageB(); } return null; }
在Flutter側這種做法仍然比較麻煩,首先是問題3仍然存在,其次是由于Flutter目前不支持反射,必須有一個類似工廠方法的方式來創建頁面實例。
為了解決以上的問題,我們需要一套能在頁面級使用、自動維護映射的方案,注解就是一個值得嘗試的方向。我們的路由注解方案annotation_route應運而生,整個注解方案的運行系統如圖所示:
讓我們從dart注解開始,了解這套系統的運作。
02 dart注解
注解,實際上是代碼級的一段配置,它可以作用于編譯時或是運行時,由于目前Flutter不支持運行時的反射功能,我們需要在編譯期就能獲取到注解的相關信息,通過這些信息來生成一個自動維護的映射表。那我們要做的,就是在編譯時通過分析dart文件的語法結構,找到文件內的注解塊和注解的相關內容,對注解內容進行收集,最后生成我們想要的映射表,這套方案的構想如圖示:
在調研中發現,dart的部分內置庫加速了這套方案的落地。
03 Source_gen
dart提供了build、analyser、source_gen這三個庫,其中source_gen利用build庫和analyser庫,給到了一層比較好的注解攔截的封裝。從注解功能的角度來看,這三個庫分別給到了如下的功能:
build庫:整套資源文件的處理
analyser庫:對dart文件生成完備的語法結構
source_gen庫:提供注解元素的攔截
這里簡要介紹下source_gen和它的上下游,先看看我們捋出來的它注解相關的類圖:
source_gen的源頭是build庫提供的Builder基類,該類的作用是讓使用者自定義正在處理的資源文件,它負責提供資源文件信息,同時提供生成新資源文件的方法。source_gen從build庫提供的Builder類中派生出了一個自己的builder,同時自定義了一套生成器Generator的抽象,派生出來的builder接受Generator類的集合,然后收集Generator的產出,最后生成一份文件,不同的派生builder對generator的處理各異。這樣source_gen就把一個文件的構造過程交給了自己定義的多個Generator,同時提供了相對build庫而言比較友好的封裝。
在抽象的生成器Generator基礎上,source_gen提供了注解相關的生成器GeneratorForAnnotation,一個注解生成器實例會接受一個指定的注解類型,由于analyser提供了語法節點的抽象元素Element和其metadata字段,即注解的語法抽象元素ElementAnnotation,注解生成器即可通過檢查每個元素的metadata類型是否匹配聲明的注解類型,從而篩選出被注解的元素及元素所在上下文的信息,然后將這些信息包裝給使用者,我們就可以利用這些信息來完成路由注解。
04 annotation_route
在了解了source_gen之后,我們開始著手自己的注解解析方案annotation_route剛開始介入時,我們遇到了幾個問題:
只需要生成一個文件:由于一個輸入文件對應了一個生成文件后綴,我們需要避免多余的文件生成
需要知道在什么時候生成文件:我們需要在所有的備選文件掃描收集完成后再能進行映射表的生成
source_gen對一個類只支持了一個注解,但存在多個url映射到一個頁面
在一番思索后我們有了如下產出
首先將注解分成兩類,一類用于注解頁面@ARoute,另一類用于注解使用者自己的router@ARouteRoot。routeBuilder擁有RouteGenerator實例,RouteGenerator實例,負責@ARoute注解;routeWriteBuilder擁有RouteWriterGenerator實例,負責@ARouteRoot注解。通過build庫支持的配置文件build.yaml,控制兩類builder的構造順序,在routeBuilder執行完成后去執行routeWriteBuilder,這樣我們就能準確的在所有頁面注解掃描完成后開始生成自己的配置文件。
在注解解析工程中,對于@ARoute注解的頁面,通過RouteGenerator將其配置信息交給擁有靜態存儲空間的Collector處理,同時將其輸出內容設為null,即不會生成對應的文件。在@ARoute注解的所有頁面掃描完成后,RouteWriteGenerator則會調用Writer,它從Collector中提取信息,并生成最后的配置文件。對于使用者,我們提供了一層友好的封裝,在使用annotation_route配置到工程后,我們的路由代碼發生了這樣的變化:
使用前:
import 'testa.dart' import 'testb.dart' import 'testc.dart' import 'testd.dart' import 'teste.dart' import 'testf.dart' class Router { Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { if(urlString == 'myapp://testa') { return TestA(urlString, query); } else if(urlString == 'myapp://testb') { String absoluteUrl = Util.join(urlString, query); return TestB(url: absoluteUrl); } else if(urlString == 'myapp://testc') { String absoluteUrl = Util.join(urlString, query); return TestC(config: absoluteUrl); } else if(urlString == 'myapp://testd') { return TestD(PageDOption(urlString, query)); } else if(urlString == 'myapp://teste') { return TestE(PageEOption(urlString, query)); } else if(urlString == 'myapp://testf') { return TestF(PageFOption(urlString, query)); } return DefaultWidget; } }
使用后:
import 'package:annotation_route/route.dart'; class MyPageOption { String url; Map<String, dynamic> query; MyPageOption(this.url, this.query); } class Router { ARouteInternal internal = ARouteInternalImpl(); Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { ARouteResult routeResult = internal.findPage(ARouteOption(url: urlString, params: query), MyPageOption(urlString, query)); if(routeResult.state == ARouteResultState.FOUND) { return routeResult.widget; } return DefaultWidget; } }
關于Flutter路由管理代碼這么長怎么高效解決就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。