在【探討UI架構(gòu)演化】一文中介紹,在UI架構(gòu)發(fā)展到一定階段,為了實(shí)現(xiàn)UI和業(yè)務(wù)邏輯解耦,引入了UI State。
UI State(UI 狀態(tài))指能夠完整決定UI長(zhǎng)什么樣的“最小”信息集合。
UI State引入后,可以說(shuō)UI架構(gòu)的演化就是圍繞它進(jìn)行的,統(tǒng)一狀態(tài)管理解決狀態(tài)分散問(wèn)題;引入U(xiǎn)I State 狀態(tài)機(jī),使?fàn)顟B(tài)變化必須可追蹤、可推導(dǎo);構(gòu)建聲明式UI框架,使得UI可以自動(dòng)從狀態(tài)生成等等。
UI State是如此重要,但如何給UI State一步步建模的文章卻很少,本文就來(lái)一起探討下這個(gè)問(wèn)題。
在探討這個(gè)話題之前,我們有必要再次深挖下 UI State到底是什么,它具備哪些核心特性。
一、UI State是什么?
狀態(tài)(State)是在設(shè)計(jì)者的認(rèn)知下,能夠完整決定 UI長(zhǎng)什么樣的最小信息集合。
它具備四個(gè)特性:
- 充分性:“State+UI設(shè)計(jì)完全可推導(dǎo)出界面;
- 最小性:沒(méi)有冗余信息,任何一個(gè)信息都不能由其他信息推斷而來(lái);
- 一致性:不存在“非法組合”,就是說(shuō)要避免狀態(tài)字段組合出“不可能業(yè)務(wù)”狀態(tài);
- 可演化性:當(dāng)需求變化時(shí),State 能以“最小范圍、最少修改、最低風(fēng)險(xiǎn)”完成新增能力。
注意:
- 由于設(shè)計(jì)者認(rèn)知有限,所以這些特性都是相對(duì)的:UI State 建模不是“一次性正確”,而是一個(gè)不斷逼近最優(yōu)的過(guò)程。
-
UI State是狀態(tài)自由度,UI設(shè)計(jì)是視覺(jué)自由度,State 決定“為什么變”,設(shè)計(jì)決定“怎么變”,** 若一個(gè)變量在UI設(shè)計(jì)變的時(shí)候就要改變,那就不能加入到UI State中。**
?比如:
- 在游戲中, 高興是狀態(tài),每個(gè)角色高興起來(lái)什么樣子是UI設(shè)計(jì);
- 在天氣App中,陰、晴、雨、雪是天氣類型狀態(tài),陰天、晴天、雨天、雪天圖標(biāo)或者頁(yè)面什么樣子是UI設(shè)計(jì)。
二、UI State建模流程
UI State 建模 = 從“UI變化”反推“變化原因”,再?gòu)脑蛑泻Y選“最小控制變量集合”。
有4個(gè)關(guān)鍵流程:
① 看變化(UI有哪些不同狀態(tài))
② 找觸發(fā)(這些變化為什么發(fā)生)
③ 抽語(yǔ)義(抽象出“業(yè)務(wù)含義”)
④ 壓狀態(tài)(只保留最小集合)
接下來(lái)我以一個(gè)手機(jī)天氣頁(yè)面為例來(lái)具體展開(kāi):

?? Step 1:列出所有的UI變化
這一步,我們要從UI交互中把“會(huì)變化的東西”全部列出來(lái)。
目前我總結(jié)一套方法,按照這套方法基本可以保證找全變化不遺漏。
1、拿到UI 交互稿,從上到下,從左到右,進(jìn)行掃描,觀察和思考每一項(xiàng)是否會(huì)變化;
2、對(duì)于每一項(xiàng)UI思考進(jìn)行4個(gè)維度思考,來(lái)確定有哪些變化:
- 業(yè)務(wù)數(shù)據(jù)變化(Data)
?? 這個(gè)UI 組件會(huì)不會(huì)因?yàn)椤皹I(yè)務(wù)數(shù)據(jù)”變化而變化?比如溫度、天氣類型。
- 數(shù)據(jù)過(guò)程(LoadState)
?? 這個(gè)UI 組件 會(huì)不會(huì)因?yàn)椤皵?shù)據(jù)狀態(tài)”不同而變化?比如loading態(tài)、error態(tài)、成功態(tài)
- 交互狀態(tài)(Interaction)
?? 這個(gè)UI組件會(huì)不會(huì)因?yàn)?“用戶行為”不同而變化?比如 選中、展開(kāi)/收起、滑動(dòng)
- 系統(tǒng)狀態(tài)
??這個(gè)UI 會(huì)不會(huì)因?yàn)椤跋到y(tǒng)和環(huán)境”不同而變化?比如系統(tǒng)主題變了,系統(tǒng)定位城市變化了
3、若這個(gè)變化會(huì)因UI設(shè)計(jì)不同而變,那就不能加入變化清單中。
最后輸出的是→UI變化清單 ?
↓
?? 主區(qū)域
城市名稱
當(dāng)前溫度
天氣描述
?? 環(huán)境信息
當(dāng)前PM2.5
當(dāng)前UV(紫外線強(qiáng)度)
當(dāng)前風(fēng)力
?? 曲線區(qū)域
溫度曲線
天氣曲線
風(fēng)力曲線
空氣質(zhì)量曲線
當(dāng)前時(shí)間標(biāo)記
?? 數(shù)據(jù)過(guò)程狀態(tài)變化
加載中
加載失敗
加載成功
? Step 2:逐個(gè)問(wèn)“為什么會(huì)變”
?? 對(duì)每個(gè)UI變化問(wèn):
? 是什么導(dǎo)致它變?
?? 主區(qū)域
溫度變化 → 溫度數(shù)據(jù)變化
天氣描述 → 天氣類型數(shù)據(jù)變化
城市變化 → 定位 +用戶選擇
?? 環(huán)境信息
PM2.5 → 空氣質(zhì)量數(shù)據(jù)
UV → 天氣紫外線數(shù)據(jù)
風(fēng) → 風(fēng)力數(shù)據(jù)
?? 曲線
溫度曲線變化 → 小時(shí)天氣數(shù)據(jù)
當(dāng)前溫度 → 當(dāng)前時(shí)間
?? 狀態(tài)
loading 加載中 → 數(shù)據(jù)請(qǐng)求過(guò)程中
error 錯(cuò)誤 → 數(shù)據(jù)請(qǐng)求請(qǐng)求失敗
success 成功→ 數(shù)據(jù)請(qǐng)求請(qǐng)求成功
?? 到這里你得了→“UI變化原因”集合。
?? Step 3:合并“同類原因”
把“本質(zhì)相同或者屬于一類的原因”做抽象+合并
溫度/ 天氣類型/風(fēng)力/PM2.5/UV
↓
??都屬于:天氣數(shù)據(jù)的一個(gè)維度
↓
?? 合并+抽象為
↓
WeatherData
loading / success / error
↓
??本質(zhì)都是:數(shù)據(jù)加載狀態(tài)
↓
?? 抽象為:LoadState
當(dāng)前時(shí)間
↓
CurrentTime
定位中/定位成功/定位失敗
↓
合并為:LocationState
?? Step 4:識(shí)別“獨(dú)立變化維度”(關(guān)鍵能力)
?? 問(wèn)一個(gè)核心問(wèn)題:
它們之間會(huì)互相決定嗎?就是說(shuō) 把某一個(gè)數(shù)據(jù)去掉,是否可以由其他數(shù)據(jù)推算出來(lái)。
| 狀態(tài) | 是否獨(dú)立 |
|---|---|
| WeatherData | ? |
| LoadState | ? |
| LocationState | ? |
| CurrentTime | ? |
?? 這些是:
? 獨(dú)立維度(正交)
?? Step 5:每個(gè)維度內(nèi)部建模
① WeatherData(核心數(shù)據(jù))
data class WeatherData(
val currentTemp: Int,
val minTemp: Int,
val maxTemp: Int,
val weather: String,
val feelsLike: Int,
val pm25: Int,
val uvLevel: String,
val wind: String,
val airQuality: String,
val hourly: List<HourlyWeather\> )
② LoadState(互斥)
sealed class LoadState {
object Loading
object Success
data class Error(val message: String)
}
// 注意 sealed 類似枚舉 狀態(tài)只能是其中一個(gè)
③ LocationState(互斥)
sealed class LocationState {
object Locating
data class Success(val city: String)
object Failed
}
④ CurrentTimeState
data class CurrentTimeState(
val currentHourIndex: Int
)
?? Step 6:組合為一個(gè)(不是混合)
data class WeatherPageState(
val loadState: LoadState,
val locationState: LocationState,
val weatherData: WeatherData?,
val currentTimeState: CurrentTimeState
)
小結(jié)
UI State 建模是一個(gè)逐步抽象的過(guò)程:從UI交互現(xiàn)象出發(fā),追溯原因,合并抽象,拆分維度,最終組為為一個(gè)最小且正交的狀態(tài)集合。