聲明式UI其實並不是近幾年的新技術,但是近幾年聲明式UI框架非常的火熱。單說移動端,跨平臺方案有:RN、Flutter。iOS原生有:SwiftUI。android原生有:compose。可以看到聲明式UI是以後的前端發展趨勢。而狀態管理是聲明式UI框架的重要組成部分。 ...
1 背景介紹
1.1 聲明式ui
聲明式UI其實並不是近幾年的新技術,但是近幾年聲明式UI框架非常的火熱。單說移動端,跨平臺方案有:RN、Flutter。iOS原生有:SwiftUI。android原生有:compose。可以看到聲明式UI是以後的前端發展趨勢。而狀態管理是聲明式UI框架的重要組成部分。
1.2 聲明式UI框架的狀態
在移動端之前的命令式UI框架,沒有狀態的概念。每個控制項其實都是無狀態的,我們要更新UI需要手動的去set。命令式UI引入狀態的概念,狀態可以理解為訂閱了控制項所依賴數據的變化,當一個控制項依賴的數據發生變化時,自動刷新UI展示。最大的優勢就是可以很方便的做到UI和邏輯的解耦。
2 provider狀態管理
2.1 使用方式
定義一個頁面如下:
實現功能,當點擊“按鈕”的時候,更新“你好”這個組件
頁面部分代碼實現(基於StatelessWidget實現):
model部分實現:
2.2 問題和不足
點擊“按鈕”的時候查看頁面刷新,發現下表羅列的Widget都執行了刷新操作,使用Selector雖然被包裹的內容沒有刷新,但是需要進行校驗操作。
2.2.1 控制項刷新
2.2.2 問題分析
- 使用不太靈活,想要消費事件刷新UI必須有頂層的Provider提供model,在一些複雜場景可能會增加邏輯複雜度
- 狀態刷新,不能實現最小粒度的管理
- 代碼不夠簡潔
3 新的狀態管理方式實踐
3.1 使用方式
實現同樣的上述頁面邏輯,代碼如下(同樣基於StatelessWidget實現):
首先不需要依賴外部的provider提供Model,任何想要獨立刷新的區域使用TosObWidget控制項包裹即可,使用比較靈活,我們可以把TosObWidget插入到任何我們想要的位置(包括provider內),代碼邏輯比較簡潔
model實現:
model的實現更加簡潔,不需要繼承ChangeNotifier,所以可以把狀態數據定義在任何我們想要的地方,使用.tos擴展屬性返回一個包含預設值的RxObj對象,當我們使用set方法更改RxObj的value的時候,通知依賴此對象的TosObWidget區域進行刷新,例:我們點擊按鈕的時候,_model.textA.value = “你好${_model.i++}”,執行後就會刷新依賴textA的TosObWidget(() => Text(_model.textA.value))區域
查看刷新狀態(與provider對比):
對比發現TosObWidget這種方式,只有依賴的數據發生變化的TosObWidget才會更新狀態,可以實現狀態刷新粒度最小化,提高性能
3.2 設計思路
3.2.1 TosObWidget
首先是使用入口,定義一個TosObWidget控制項,入參為build函數,返回widget,每個TosObWidget就是一個可獨立進行狀態刷新的區域
TosObWidget控制項的實現如下:
TosObWidget的build函數為重載的其父類_ObzWidget的build函數,最終會被_ObzWidget的_ObzState調用,_ObzWidget的實現如下:
接下來查看_ObzState的實現,主要邏輯都在這個類進行實現,這裡貼出所有的代碼(註意框起來的邏輯):
3.2.2 TosObWidget邏輯分析
- 首先_ObzState依賴一個RxObserver _observer變數
- RxObserver _observer這個 變數持有了_updateUI()這個方法,最終會通過這個方法刷新TosOBWidget的狀態
- 當TosObWidget執行build的時候,會通過一個靜態變數RxObserver.proxy把_observer共用出去
- 這樣TosObWidget包裹的內容,使用RxObj的getValue的時候會拿到被共用的_observer,這時建立RxObj和TosObWidget的聯繫
- 聯繫建立後,重置共用變數RxObserver.proxy
- 這樣在RxObj的value執行set方法時,會調用到與其綁定的TosObWidget的_updateUI()這個函數
3.2.3 RxObj的實現
如下貼出RxObj的value的get和set函數:
- 當執行RxObj的value的get方法時,代碼如下,拿到 RxObserver的靜態成員變數proxy,類型為RxObserver(即為上一步TosObWidget共用出來的_observer)
- 判斷RxObserver.proxy不為空,且沒有被添加到_observers列表( List _observers),則添加
- 當執行RxObj的value的set方法時,校驗value是否與當前的value值相同,且判斷是否是首次創建(首次創建不會執行狀態刷新)
- 校驗完成後則賦值執行refresh()函數,更新TosObWidget的狀態
refresh()函數的實現如下:
observer.update()函數即為執行與Rxobj關聯的TosObWidget的_updateUI()函數:
看下RxObserver的實現:
註意框起來的邏輯,update函數即上面_ObzState的_updateUI()函數的引用
至此整個實現流程已經貫通了,接下來看下如何使用:
1)通過.tos擴展屬性定義RxObj變數:
2).tos擴展屬性的實現如下:
3)如果要創建一個預設值為空的,RxObj實例,使用如下方式:
此時如果我們使用RxObj的setValue方法,就會刷新依賴它的所有TosObWidget控制項,如果有些情況下,沒有調用setValue方法,但是需要刷新狀態,可手動調用refresh()方法,實現如下:
至此,就完成了TosObWidget控制項的狀態刷新
4 總結
註:基於本文示例的功能邏輯進行對比
作者:京東物流 張俊飛
來源:京東雲開發者社區