专栏名称: 前端外刊评论
最新、最前沿的前端资讯,最有深入、最干前端相关的技术译文。
目录
相关文章推荐
商务河北  ·  经开区“美•强•优”三重奏 ·  10 小时前  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
前端早读课  ·  【第3451期】前端 TypeError ... ·  2 天前  
51好读  ›  专栏  ›  前端外刊评论

SwiftUI:Better apps. Less code.

前端外刊评论  · 公众号  · 前端  · 2019-08-22 08:59

正文

本文中所有 Demo 运行的环境依赖

  • macOS Catalina 10.15 Beta

  • Xcode 11 Beta5

前言

近年来 Android 和 iOS 开发在两家上游商业公司的推动下,不断尝试着一轮一轮的变革:

  • Google 2011 年发布 Dart,2017 年发布 Flutter,同年 Google I/O 2017 上宣布 Android 官方支持 Kotlin, Google I/O 2019 上宣布 Kotlin-first,同时发布 Kotlin Jetpack Compose。

  • Apple 在 2014 WWDC 上发布 Swift,在 2019 WWDC 上发布 SwiftUI。

其中很多理念和前端社区的走向也是相同的,比如:

  • 声明式 UI

  • 响应式数据

本文会简要分享下 SwiftUI 的上面两点理念,以及介绍下 Xcode 最新的一些工具设计。

第一印象

如果你还不知道 SwiftUI 是什么,那也没关系,反正我也不打算从 SwiftUI 开始讲起...

为了让广大前端工程师同学打起兴趣,首先我打算先介绍下 SwiftWebUI,它是 Github 上一个开源的将 SwiftUI 跑在 Web 上 的实验性项目。

看看官方的一个简单计数器 🌰:

  • 创建一个空的 macos command line 应用

  • 使用 Swift Package Manager 从 github 上拉取 SwiftUIWeb 依赖包

Swift 的包管理是基于源文件 + 配置文件的,也遵循 semver 规则。

  • main . swift 粘贴如下代码,最后添加一行启动服务 SwiftWebUI . serve ( MainPage ())

  • 点击 Xcode build,在浏览器打开 http : //localhost:1337

效果:

从上面这个小例子中就可以看到到 SwiftUI 的几个重要理念了:

  • 声明式 UI (类比 React JSX,Flutter)

  • 响应式数据 (类比 mobx,vue)

  • “CSS In JS”

  • 链式调用(官方术语叫 ViewModifier,类似 react 里面的 HOC 概念)

更复杂的 Demo 可以体验:https://github.com/swiftwebui/AvocadoToast

声明式 UI

Demo

Demo 参考:https://developer.apple.com/tutorials/swiftui/creating-and-combining-views

SwiftUI 的文档这次设计了非常精美的 tutorials 交互,可以跟着一步一步的操作。

  • 所有视图组件类型都是 View ,它只需要实现一个接口 body ,可以类比为 React Component 的 render

  • VStack 是垂直布局的容器, HStack 是水平布局的容器, ZStack 是独立图层的容器,类似前端 zindex

  • 组件可以有属性(Text 的参数),组件也可以应用 ViewModifier(链式调用)

对 SwiftUI 视图语法的深入分析可以阅读:SwiftUI 的一些初步探索 (一)

DSL

WWDC-2019 Session 216: SwiftUI Essentials

SwiftUI 和 Flutter 的 UI 语法都可以看见 React JSX 的影子,它们不约而同使用各自的声明式 DSL 来描述 UI 。但是这和前端常用的模板语言(Nunjucks/Vue)还是有一些区别的,它们都是复用语言本身的逻辑控制语句(条件,循环),再加上一种特殊的数据结构(eg: React Virtual DOM)来表达 UI。毕竟对于 Google 和 Apple 来说,语言、编译器、开发工具、开发者、平台这整条链路都在掌控中,那么可以做一些集成度更高的设计。

我认为使用类似 Nunjucks 此类模版语言的一些缺点:

  • 和业务逻辑的编程语言之间有一层 gap,发生关联的地方会丢失编辑器智能提示,跳转等上下文信息 (通过开发编辑器插件等也可以实现,但成本较高)

  • 模板内部的属性绑定,循环,变量插值一些场景会出现字符串形态的逻辑代码

  • 模板语言仍需要一次提前编译,比如 vue 模板的编译:https://vuejs-tips.github.io/compiler/#v-html

仔细看一下 SwiftUI 的一些视图语法:

条件:直接使用条件控制语句

循环:直接使用循环控制语句

List:

如果是动态 List 的场景下,SwiftUI 还会要求开发者提供 Identifiable:类似 react 的 key 或者 antd Table.props.rowKey, 其作用是让框架可以精确定位和更新 List 中的某一行元素

使用子元素来表达结构:

Form: 此外按 从 SwiftUI 谈声明式 UI 与类型系统 这篇文章的分析,因为 SwiftUI 强类型的,那么一个组件可能的几种视图结构是可以被静态分析出来的,那么在运行时甚至可以基于静态分析的类型信息来对视图的更新算法做优化,可以达到比 React Vitual DOM 更好更新性能。

ViewModifier

官方的文档这么介绍 ViewModifier :A modifier that you apply to a view or another view modifier, producing a different version of the original value.

它的用法有点类似 React 的 HOC(High Order Component),附加在某个视图上改变它原本的结构或样式。除了内置的 modifier,SwiftUI 也允许开发者定制自己的 modifier 的,比如下面是一个自定义例子:

而且 modifier 是应用于整颗子树的,例如下面这两种写法都可以实现禁用整个表单的效果:

Stack & 布局

Stack 前文已经介绍过,是内置的一些布局容器

  • HStack:水平排布它的子元素的一个容器视图。

  • VStack:垂直排布它的子元素的一个容器视图。

  • ZStack:层叠排布它的子元素的一个容器视图。

  • Spacer:一个弹性的空白间距元素,会充满容器元素的主轴方向

  • Divider:一个虚拟的分界元素 完整列表可以参考:View Layout and Presentation

虽然没有真实开发过 ios 应用,但是从 App Store 里面的 App 设计风格来看,iOS 由于屏幕尺寸小,单页内部的元素层级不多,布局也不会太复杂,并且时常搭配 NavigationView,TabView 这种导航类的组件继续对视图做细粒度切割。而且 Apple 对设计规范的掌控力非常强,因此 SwiftUI 的布局体系相对比 Web 要简单很多,有点类似简化版的 Flex。

系列文章深度解读|SwiftUI 背后那些事儿 这篇文章对 SwifUI 布局算法有一部分解释

跨平台?

内置组件是有跨平台的不同实现的,有点类似当初 React Native 的做法:

但是 Apple 官方是说为了用户体验考虑,不推荐为 Mac,iPhone,Apple Watch, Apple TV 这些不同尺寸的设备采用一套界面设计,因此他们不推崇 Write once , run anywhere ,而是宣称 Learn once , apply anywhere

响应式数据

响应式数据在前端领域已经有非常多的实现了,这里只大致介绍一下:

What?

假设抽象的认为组件就是消费一些数据去生成特定区块视图的逻辑封装体,那么 Component 都是满足 State => View 这么一个协议的封装。而响应式数据的最重要效果就是 当依赖的数据(State)发生变化的时候,组件视图(View)会自动更新。

Why?

那么为什么要采用这种开发模式呢?下面这张图来自 WWDC 2019 - Session 204:Introducing SwiftUI: Building Your First App , 诠释得非常好。

GUI 程序通常使用场景是处理一个长时间段的用户交互过程,这其中涉及到许多用户输入信息,视图临时状态的存储。此外整个页面树上可能有数以百计的元素之间要互相通信和联动。如果按照传统的事件驱动的编程模型,依靠程序员的大脑去掌控所有数据到视图,数据到数据之间的衍生依赖关系,脑力负担是非常大的,一旦这个复杂度超越了人脑的算力,就会导致关联关系不正确,也就是 Bug 的产生。

而“响应式”就是将这个依赖关系的维护交由框架来处理,开发者只需要“声明”出依赖关系,而且这个“声明”是指用对业务逻辑的描述语言来顺带地描述出这种依赖关系,比如:

  • State => View 状态到视图的过程中,生产视图的过程中消费(getter)了哪些数据,就可以大体认为是该视图对该数据建立了依赖

  • State => Drived State 衍生状态,比如计算属性的定义过程中,该属性的终值依赖了哪些中间数据,就可以认为是该属性对其他属性建立了依赖

在这个理论体系下,State 始终是 single source of truth,视图只是数据的一个衍生值(Derived Value)。

SwiftUI 用响应式状态管理和数据绑定避免了大量的命令式控制逻辑,从而使应用开发者用 “数据驱动” 或 “模型驱动” 的替代传统的事件驱动,开发者的脑海中始终在思考此刻控制我的视图的数据状态是如何,而背后衍生的视图如何更新,则不用关注。

当然这种模式也有一些弊端,参阅:vue.js 会是那颗银弹吗?

How?

以前端领域的 Observable 实现库 @nx-js/observer-util 的 Demo 为例子:

可以看出这里分几部分:

  • 定义响应式数据:想方设法利用语言特性实现对对象的读写(getter,setter) 做拦截

  • vue@2 是使用 JS defineProperty 特性

  • observer-util 和 Vue@3 都是利用 JS Proxy 特性

  • SwiftUI 是利用 Swift 语言的 Property Wrapper 特性,@state @Binding 在 swift 语言标准里都属于Property Wrapper 的实现

  • 建立依赖关系:observer-util 是通过在 function 执行的前后打上标志位,记录下整个方法执行过程中的依赖关系 key-value map,其 key 是发生 getter 的 JS 数据对象,value 是 function 本身

完成上面两步之后,一旦框架监听到某个对象的 setter,就从依赖的 key-value Map 里面找到当前对象的依赖函数列表,一一执行。

更详细的解读可观看:WWDC 2019 - Session 226: Data Flow Through SwiftUI

SwiftUI 的状态管理 API:

  • @State:定义一个响应式状态,它的变化会导致依赖它的视图自动更新(单向)

  • @Binding:视图和数据的双向绑定

  • @ObjectBinding:作用等价于 @Binding,但是支持使用一个外部对象

  • @BindableObject、Combine:Apple 官方新发布的 combine, 说是用来处理外部事件或服务端推送等场景, 其实笔者也不懂这是个啥,但只要说它是 RxJS 的 Swift 版本实现,大家应该就会心一笑吧 🚗

  • @EnviromemntObject:沿着 View 树的层级一直向下共享的数据,实现类似 Scoped Data 的效果

  • @Enviroment:可以全局共享的数据,同时它的变化会导致 UI 自动刷新,类似 React 这边的 Provider

Xcode 工具设计

首先 Xcode 一直是一个非常强的 IDE,包括项目管理,依赖管理,版本管理,开发测试构建等等项目研发需要的功能它都支持。

本文主要介绍新红框圈出来与 SwiftUI 有关的一些新功能。

Live Preview

对于 UI 开发来说,能实时预览而不用泡杯咖啡等待构建真是太愉悦了,Flutter 也是将 Hot Reload当作一个很重要的卖点来打,而 SwiftUI 更是将这个体验做到了极致。

上图右下角红框的图标,点击即是在 Live Preview 和传统的 Build Simulator Preview 之间切换,Apple 把这两种预览都做到了一个位置,just click to switch!

  • Live Preview:基于 AST 分析,无构建流程,实时刷新,无交互(不能点击和跳转)

  • Simulator Preview:传统构建流程,真实应用运行(和传统预览一样,但不用切换屏幕到模拟器了)

为什么 Live Preview 能做到极其快?

  • 对当前文件进行 AST 分析, 找到所有遵守 PreviewProvider 协议的类型进行预览渲染,只展示静态的视图结构

  • 监听代码或画布的变化,只差量更新最细粒度变化的元素部分







请到「今天看啥」查看全文