从Windows到Web,到Android,到iOS,我们编写应用 (App) 最初的方式都是命令式的 (Imperative Style) 。但随着声明式 (Declarative Style) 的出现,情况正在发生极速的变化。我们先看看命令式和声明式分别怎么写UI。
命令式的方式是一种自然而然想到的方式,我们写代码构建整个UI (view tree),在需要更新UI时 (比如获取到网络数据后或者用户改变UI元素后) 从view tree里查找到对应的元素,再更新它。比如在Android开发中,我们通过findViewById查找到某文本元素,再给它设置显示的内容:
TextView tv = findViewById(R.id.text_view);
tv.setText("Hello World");
命令式的方式是一种最朴素的方式,它要求我们针对每一次UI的变更都亲自写代码修改view tree,且这些代码分散在各个地方。那么我们有没有方法把我们从对view tree的频繁操作中解放出来呢?有。Facebook的React框架就是一个知名的先驱。声明式的方式集中在一个地方声明UI的结构,UI的变化通过数据来驱动。我们只需要修改数据,剩下的修改view tree的工作交给框架来做。
其实声明式的UI布局方式已经有比较长的历史,下面简单介绍一些比较知名的
前端开发在React之前,比较常用的是jQuery这种命令式的框架。下面是一个jQuery的例子,用ajax发起一个网络请求,请求返回结果后在DOM树里搜索到节点 ($("#weather-temp")) ,再将结果设置给节点显示出来。
$.ajax({
url: "/api/getWeather",
data: {
zipcode: 97201
},
success: function( result ) {
$("#weather-temp").html( "" + result + " degrees" );
}
});
Facebook内部从2011年开始开发FaxJS,也就是React的原型,并于2013年开源了React。下面是一个React声明式UI的例子
class MarkdownEditor extends React.Component {
constructor(props) {
super(props);
this.md = new Remarkable();
this.handleChange = this.handleChange.bind(this);
this.state = { value: 'Hello, **world**!' };
}
handleChange(e) {
this.setState({ value: e.target.value });
}
getRawMarkup() {
return { __html: this.md.render(this.state.value) };
}
render() {
return (
Input
Output
);
}
}
root.render( );
渲染效果:
我们将某个Component的UI描述全部放在render方法里,这个Component有它自己的状态 (state)。当用户在textarea输入内容时,handleChange会被调用,它会从textarea取出当前值,赋给value这个state变量,调用setState方法告诉React框架需要更新UI,然后框架就会完成剩下的所有工作。对开发者来说非常的简单高效。
Facebook在2015年开源了React Native这个跨平台的框架,它可以让你通过前端技术栈开发Android和iOS应用。它声明UI的方式和React一模一样,如下所示
import React from 'react';
import {Text, View} from 'react-native';
import {Header} from './Header';
import {heading} from './Typography';
const WelcomeScreen = () => (
Step One
Edit App.js to change this screen and turn it
into your app.
See Your Changes
Press Cmd + R inside the simulator to reload
your app’s code.
Debug
Press Cmd + M or Shake your device to open the
React Native Debug Menu.
Learn
Read the docs to discover what to do next:
);
Facebook在2017年开源了Litho这个高性能的Android UI开发框架,在开源之前已在内部主要App广泛使用,如Facebook等。受React的影响,它也采用了声明式的UI布局方式。下面是一个例子
class PostStyledKComponent(val post: Post) : KComponent() {
override fun ComponentScope.render(): Component {
return Column {
child(
Row(alignItems = YogaAlign.CENTER, style = Style.padding(all = 8.dp)) {
child(
Image(
drawable = drawableRes(post.user.avatarRes),
style = Style.width(36.dp).height(36.dp).margin(start = 4.dp, end = 8.dp)))
child(Text(text = post.user.username, textStyle = Typeface.BOLD))
})
child(
Image(
drawable = drawableRes(post.imageRes),
scaleType = ImageView.ScaleType.CENTER_CROP,
style = Style.aspectRatio(1f)))
}
}
}
渲染效果:
Google在2018年发布了Flutter的第一个稳定版本v1.0.0。它采用了声明式的布局方式,下面是一个简单的例子
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
Apple在2019年发布了SwiftUI的第一个版本,它让你用声明式的方式来写iOS应用。下面是一个简单的例子
import SwiftUI
struct AlbumDetail: View {
var album: Album
var body: some View {
List(album.songs) { song in
HStack {
Image(album.cover)
VStack(alignment: .leading) {
Text(song.title)
Text(song.artist.name)
.foregroundStyle(.secondary)
}
}
}
}
}
Google在2021年7月发布了Jetpack Compose的第一个稳定版本v1.0,像Flutter一样采用声明式UI布局方式,但Compose的设计比Flutter更合理一些。在Flutter里,你要给一个Widget设置宽高等属性都需要在外面套一层Container来实现,这非常的臃肿,而其它声明式的框架,包括Compose,大都可以通过节点的属性来设置。下面是一个Compose的简单例子
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
fun MessageCard(msg: Message) {
// Add padding around our message
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = "Contact profile picture",
modifier = Modifier
// Set image size to 40 dp
.size(40.dp)
// Clip image to be shaped as a circle
.clip(CircleShape)
)
// Add a horizontal space between the image and the column
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = msg.author)
// Add a vertical space between the author and message texts
Spacer(modifier = Modifier.height(4.dp))
Text(text = msg.body)
}
}
}
渲染效果:
我们看到2017年的Litho,2018年的Flutter,2019年的SwiftUI,2021年的Jetpack Compose都采用了声明式的UI布局方式,已呈燎原之势,声明式的UI布局方式未来会像前端领域的React一样成为主流吗?