UDF(Unidirectional Data Flow,单向数据流)是一种软件架构模式,广泛应用于现代前端和移动端开发中,尤其是在状态管理和用户界面处理方面。其核心思想是通过单一方向的数据流动来管理应用程序的状态和数据变化,确保数据的可预测性和可追溯性。UDF 的结构通常包括以下几个核心部分:
-
意图(Intent):
- 用户的交互或系统的事件被封装为“意图”,表示用户希望执行的操作或发生的事件。例如,按钮点击、页面加载等事件都可以看作是意图。
-
操作(Action):
- 意图被转换为操作,这些操作是对状态进行更改的具体行为。操作是纯粹的数据结构,描述了要做的事情,但不包含任何业务逻辑。
-
状态(State):
- 状态是应用程序当前的数据快照,表示 UI 应该展示的内容。状态在整个应用中是不可变的,唯一能够更改状态的方式是通过触发操作。
-
处理器(Reducer):
- 处理器负责接收操作并根据这些操作生成新的状态。处理器是纯函数,意味着它在不修改输入的情况下,返回新的状态对象。
-
视图(View):
- 视图观察状态的变化,并根据最新状态更新 UI。这确保了 UI 总是与当前状态同步。
UDF 的工作流程:
- 用户触发一个 意图(如点击按钮)。
- 该意图被发送到处理层,被转换为一个 操作。
- 处理器 根据操作生成一个新的 状态。
- 视图 根据新状态更新 UI。
- 任何后续的用户操作或系统事件会重复这个流程。
直接写一个小demo便于理解:
创建CountIntent.kt
这里使用sealed限制继承,只允许同一个文件内定义之类。
sealed class CountIntent {
object Increment : CountIntent()
object Decrement : CountIntent()
}
创建CountState.kt
data class CountState(
var count:Int = 0
)
创建CountViewModel.kt
class CountViewModel : ViewModel() {
// MutableStateFlow 是 StateFlow 的可变版本,用于内部更新状态
private val _state = MutableStateFlow(CountState())
// StateFlow 是一种冷流,适用于表示和管理单一可变的状态,总持有一个最新值,当有新值时会立即通知观察者
val state: StateFlow<CountState> = _state
// MutableSharedFlow 是 SharedFlow 的可变版本,用于在多个消费者之间共享数据流
private val _intent = MutableSharedFlow<CountIntent>()
// 初始化块,启动一个协程来收集意图并进行处理
init {
viewModelScope.launch {
// 收集 _intent 中的值,并根据意图调用对应的方法
_intent.collect { intent ->
when (intent) {
is CountIntent.Increment -> increment() // 处理增加计数的意图
is CountIntent.Decrement -> decrement() // 处理减少计数的意图
}
}
}
}
// 公有方法,供外部调用,用于发送意图
fun sendIntent(intent: CountIntent) {
viewModelScope.launch {
_intent.emit(intent) // 发射意图到 _intent 流
}
}
// 私有方法,增加计数并更新状态
private suspend fun increment() {
_state.emit(CountState(_state.value.count + 1)) // 增加计数并发射新的状态
}
// 私有方法,减少计数并更新状态
private suspend fun decrement() {
_state.emit(CountState(_state.value.count - 1)) // 减少计数并发射新的状态
}
}
最后在MainActivity.kt中调用就好了。这样一个UDF+MVI的架构demo就完成了
class MainActivity : AppCompatActivity() {
private lateinit var viewModel:CountViewModel
private lateinit var binding:ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding=ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel= ViewModelProvider(this).get(CountViewModel::class.java)
lifecycleScope.launch {
viewModel.state.collect{state->
binding.text.text=state.count.toString()
}
}
}
fun addOnClick(view : View){
viewModel.sendIntent(CountIntent.Increment)
}
fun subOnClick(view : View){
viewModel.sendIntent(CountIntent.Decrement)
}
}
还是把activity_main.xml文件贴出来吧:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:onClick="addOnClick"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+1" />
<Button
android:onClick="subOnClick"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-1" />
</LinearLayout>
其实总体还是比较简单的,这样架构主要体现分层,提高代码可维护性。