Flutter:由 TextField 來看 Widget 如何保留狀態
前言
在 Flutter 開發過程中,可以說大部分都環繞在 StatelessWidget 與 StatefulWidget 之間,無狀態的 StatelessWidget 主要是做一次性的建置(build
),Flutter 在建構這個 Widget 時會去呼叫 build 方法一次。
最常使用的文件顯示 Widget - Text 便是 StatelessWidget,所以它在繪製完文字內容之後便不能再修改。
有狀態的 StatefulWidget 則是可以重複建置(build
),它將建置動作交由 State 來處理,State 這個類別還提供一個 setState
方法,透過這個方法可以驅使 Flutter 再次呼叫 State 的 build
方法來重新建置 Widget,因此我們可以在 State 內宣告類別層級的變數來儲存資料,當build
方法重新建立 Widget 時,再將資料回填到對應的屬性,藉此達到狀態保留的功能。
最常使用的文件編輯 Widget - TextField 便是 StatefulWidget,我們每多輸入一個文字它會重新建置一次,但是他可以保留之前的內容(狀態)並將新輸入的文字累加進去。
剛說到 Text 無法重新建置,所以當呈現的文字內容要變更時,一般都是透過外層的 StatelessWidget 直接重新建立,當然如果需要保留它的狀態也必須透過外部暫存。
神奇的 TextField
我們再進一步思考 State 的 build
方法會重新建置 Widget,這意味著透過 build
方法所建立的 Widget 不論是 StatelessWidget 或是 StatefulWidget 都無法保留自己狀態,除非我們特別將狀態儲存起來。
所以我們從官網文件 Handle changes to a text field 可以看到,要保留 TextField 的狀態方法可以宣告一個變數來儲存,並在 TextField 的 onChanged
事件內將目前的內容儲存到變數內。
1 | class _MyHomePageState extends State<MyHomePage> { |
或者透過 TextEditingController 來儲存。
1 | class _MyHomePageState extends State<MyHomePage> { |
沒仔細想可能不會發現有些奇怪的地方,我們直接在專案預設範例內加入一個 TextField,而且不要幫它儲存任何狀態,主要程式碼如下:
1 | class _MyHomePageState extends State<MyHomePage> { |
因為 FloatingActionButton 在點擊時會呼叫 setState
方法,因此每次按它時就會執行 build
方法來重建 Widget,所以理論上 TextField 內容會被清空,接下來我們直接執行測試看看。
神奇的事情發生了,既使透過 build
重建 TextField,但是 TextField 的內容仍然被保留下來,看來 TextField 似乎是一個在規則外的特殊 Widget。
TextEditingController
TextEditingController 跟我們使用變數來儲存內容有什麼差別,從原始碼可以知道 TextEditingController 繼承自 ValueNotifiertext
)之外還會多儲存其他狀態。
接著我們來從 TextField 的原始碼可以看到如下圖的關係:
- State 內含
widget
屬性可以得知自己是由哪一個 StatelessWidget 所實作出來的,當然也可以藉此取得 StatelessWidget 的屬性。 - _TextFieldState 也建立一個 TextEditingController 變數
_controller
,以及一個_effectiveController
屬性,這個屬性主要是回傳我們在建立 TextField 時給予的controller
,如果我們未給予值時則以_controller
替代。
1 | class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixin { |