跳到主内容

声明式 UI 简介

解释声明式编程风格与命令式编程风格的区别。

本简介旨在阐述 Flutter 所使用的声明式风格与许多其他 UI 框架所使用的命令式风格之间的概念差异。

为什么要使用声明式 UI?

#

从 Win32 到 Web,再到 Android 和 iOS,各种框架通常都使用命令式 UI 编程风格。这可能是你最熟悉的风格——你需要手动构建一个功能完备的 UI 实体(例如 UIView 或等效组件),并在 UI 发生变化时使用方法和设置器(setters)对其进行修改。

相比之下,为了减轻开发者编写 UI 状态转换逻辑的负担,Flutter 让开发者只需描述当前的 UI 状态,而将状态转换的任务交给框架处理。

然而,这要求我们在处理 UI 的思维方式上做出一点转变。

如何在声明式框架中更改 UI

#

考虑以下简化示例

View B (contained by view A) morphs from containing two views, c1 and c2,
  to containing only view c3.

在命令式风格中,你通常会找到 ViewB 的所有者,通过选择器或 findViewById 等方法获取实例 b,并调用其修改方法(并隐式地使其失效)。例如:

java
// Imperative style
b.setColor(red)
b.clearChildren()
ViewC c3 = new ViewC(...)
b.add(c3)

你可能还需要在 ViewB 的构造函数中重复此配置,因为 UI 的事实来源(source of truth)可能会比实例 b 本身存活得更久。

在声明式风格中,视图配置(如 Flutter 的 Widget)是不可变的,仅仅是轻量级的“蓝图”。若要更改 UI,Widget 会触发自身的重建(在 Flutter 中最常见的是通过在 StatefulWidget 中调用 setState),并构建一个新的 Widget 子树。

dart
// Declarative style
return ViewB(color: red, child: const ViewC());

在这里,Flutter 不会在 UI 发生变化时修改旧的实例 b,而是构建新的 Widget 实例。框架会在后台处理传统 UI 对象的大部分职责(例如维护布局状态),其实现方式是使用RenderObjectsRenderObjectFlutter 框架中负责布局、绘制和命中测试的持久化对象。了解更多RenderObject 是一个持久的、可变的对象,负责处理布局、绘制和命中测试等繁重的工作。RenderObject 在帧之间保持持久,而 Flutter 的轻量级、不可变的 Widget 则作为蓝图,指示框架在状态切换时修改 RenderObject。剩下的工作全部由 Flutter 框架处理。