原文作者:Dan Abramov
译者:UC 国际研发 Jothy
在之前的文章中,我提到要承认我们的知识缺口。 你可能会认为我建议你们去解决平庸的问题,但这并不是我本意! 这是个广阔的领域。
我坚信你可以“随时随地开始”,不需要按照任何特定的顺序学习技术。 但我也认为获得专业知识很重要。 就个人而言,我最感兴趣的是创作用户界面。
我一直在思考我所熟知且有价值的东西是什么,诚然,我熟悉某些技术(例如 JavaScript 和 React),但从经验中习得的收获更为重要且难得,我从来没试过把它们说出来,这是我第一次尝试进行归类描述。
网络上有很多关于技术和库的“学习路线图”。 2019 年流行哪个库?2020 年呢?你应该学习 Vue 还是 React 还是 Angular?Redux 或 Rx 怎么样?你必须学习 Apollo 吗?REST 还是 GraphQL?实在太容易迷失其中了,而且如果作者错了怎么办呢?
我最大的学习突破不在于某个特定的技术。相反,当我努力解决特定的 UI 问题时,我学到的最多。 有时候,我会找到有帮助的库或模式。 其他时候,我会自己想出解决方案(有好有坏)。
这涵盖了理解问题,实践解决方案以及应用不同策略,它们为我带来了最有价值的学习体验。 这篇文章只关注问题部分。
如果你开发过用户界面,那么你可能直接或使用库来处理以下这些挑战。 无论是哪种情况,我都鼓励你尝试不使用任何库的情况下,独立创建一个小应用,并不断重构,解决问题。 任何一个问题都没有通用的解决方案,在探索问题和不同的权衡尝试之中才能学到东西。
- 一致性。 你点击️“like”按钮后,文字更新:“你和其他 3 个朋友 like 了这篇文章。”你再次点击,文本变回原样。 听起来很容易,但也许屏幕上好几个地方都有这样的标签,也许还有其它需要改变的视觉标识(例如按钮背景)。之前从服务器获取并在鼠标悬停时可见的“likers”列表,现在应包含你的姓名。 如果你导航到另一个屏幕并返回,帖子不应该“忘记”它被 like 了。 即使是实现局部的一致性也颇具挑战,但是其他用户也可能修改我们显示的数据(例如,他 like 了我们正在查看的帖子)。 我们如何在屏幕的不同部分保持数据同步? 我们如何以及何时使本地数据与服务器保持一致,反之亦然?
- 响应性(Responsiveness)。 人们能容忍的屏幕对动作的视觉反馈时间是有限的,对于手势和滚动等连续动作,这个时间会很低(即使跳过一个 16ms 的帧也感觉“很笨拙”)。对于像点击这样的离线行为,研究表明用户认为任何 <100ms 的延迟都还算快。 如果操作需要更长时间,我们就需要显示一个指示符。 但是也存在一些反直觉的挑战,导致页面布局“跳转”或经过多个加载“阶段”的指示符会使操作感觉比以前更长。 同理,以丢弃动画帧为代价,在 20ms 内处理交互可能比在 30ms 内处理它并且没有丢帧感觉更慢。大脑是非基准的,该如何让我们的应用响应不同类型的输入?
- 延迟。 计算和网络访问都需要时间。有时我们可以忽略计算成本,前提是它不会损害目标设备的响应能力(确保在低端设备频谱上测试应用)。 但处理网络延迟是不可避免的 - 它可能要花上几秒钟! 我们的应用不能只是冻结等待数据或代码加载。这意味着任何依赖于新数据,代码或资源的操作都可能是异步的,需要处理“加载”情况,但几乎每个屏幕都会发生这种情况。 我们该如何优雅地处理延迟,不给用户显示层层的旋转图标或者空页面? 我们如何避免布局“跳跃”? 我们如何在不用每次都“重新布局”代码的情况下更改异步依赖关系?
- 页面跳转(Navigation)。 我们希望与 UI 交互时它能保持“稳定”,页面上的东西不会凭空消失。 无论是在应用内发出(例如点击链接)还是由外部事件触发(例如点击“后退”按钮),页面跳转都应该遵循这一原则。 例如,在配置区域上切换 /profile/likes 和 /profile/follows 标签卡时,不应该清除标签视图之外的搜索框内容。跳转到另一个页面就像走进另一个房间。人们会希望当他们返回时,东西仍与离开时保持一致(也许还能出现一些新东西)。 如果你在一个数据流中,点击个人资料,然后返回,你可以会丢失你在数据流中的位置 - 或者等待它再次加载。该如何构建我们的应用来处理任意页面跳转,并保证不丢失重要的上下文?
- 过期。 我们可以通过引入本地缓存使“后退”按钮即时跳转,在缓存中“记住”一些数据以便快速访问,即使理论上也可以重新获取它。但缓存也有自己的问题,它可能会过期。 如果我更改了头像,它也应该在缓存中更新。如果我发布新帖子,则需要立即显示在缓存中,否则缓存应失效。这会比较困难且容易出错。 如果发布失败怎么办? 缓存在内存中保留多长时间?当我们重新获取信息流,我们是该使用缓存的“stitch”新提取的信息流,还是丢弃缓存? 分页或排序如何在缓存中表示?
- Entropy(熵,热力学函数)。 热力学第二定律说“随着时间的推移,事情变得一团糟”(好吧,不完全是这样),这也适用于用户界面。 我们无法准确预测用户交互及其顺序。无论何时,我们的应用都可能处于难以置信的可能状态。 我们尽最大努力使结果可预测并受制于我们的设计。我们不想通过错误截图去思考“为什么会这样”。 对于 N 种可能状态,它们之间存在 N×(N-1) 个可能的转换。 例如,如果一个按钮可以处于 5 种不同状态(正常,激活,悬停,危险,禁用)中的一种状态,则更新按钮的代码必须正确,以便进行 5×4=20 次可能的转换 - 或禁止其中一些转换。 我们如何控制可能状态的组合爆炸并使视觉输出可预测?
- 优先级。事情总有轻重缓急之分。 对话框可能得“出现”在产生它的按钮上方,并“突破”其容器的边界。 新计划的任务(例如,响应点击)可能比已经开始的长期任务(例如,在折叠的屏幕下方渲染下一个帖子)更重要。 随着应用的增长,由不同的人和团队编写的部分代码将竞争有限的资源,如处理器,网络,屏幕区域和包体积预算。 有时你可以在共同的“重要性”范围内对竞争者进行排名,比如 CSS 的 z-index 属性。 但它的效果通常比较差。 每个开发人员都偏向于认为他们的代码很重要。 但如果一切都很重要,那就什么都不重要了! 我们如何让独立的小组件进行合作而不是争夺资源?
- 无障碍(Accessibility)。 不支持无障碍访问的网站不是一个小问题。 例如,在英国,每五个人就有一个人身患残疾(从这里可以更直观看出:https://www.abrightclearweb.com/web-accessibility-in-the-uk/),我也亲身感受到了这一点。 虽然我才 26 岁,但我很难阅读细体字和低对比度的网站。 我尽量少用触控板,而且我害怕有一天我必须通过键盘来浏览质量低下的网站。 我们需要让有残疾的人也可以顺畅的使用我们的 App - 而且好消息是现在有很多现成的方法。首先,对于这个群体的教育和帮助他们阅读的工具可以缓解这一点,但我们还需要让产品开发者们可以轻松地从软件层面做到这一点。 我们可以做些什么,来让开发无障碍的网站变成大家的常识和一个默认就要做的事情,而不是事后才想去做呢?
- 国际化。 我们的应用程需要运行于世界各地。人们不仅会讲不同的语言,而且还需要用产品工程师的最少努力支持从右到左的布局。我们如何在不牺牲延迟和响应能力的情况下支持不同的语言?
- 传输。 我们需要将应用代码提供给用户电脑。 我们使用什么样的传输和格式? 这可能听起来很简单,但其中有许多权衡。 例如,原生应用倾向于以巨大的应用体积为代价加载所有代码。Web 应用往往具有较小的初始负荷,但代价是使用期间的延迟会更长。我们如何确定何时引入延迟? 如何根据使用模式优化传输? 需要什么样的数据才能获得最佳解决方案?
- 弹性。 如果你是昆虫学家,你可能会喜欢 bug(虫子),但你估计不会喜欢在程序中看到它们。但是,你的一些 bug 将不可避免地进入生产阶段。那会引发什么? 有些错误会导致错误但定义明确的行为。 例如,在某些情况下,你的代码可能会显示错误的输出。但是如果渲染代码崩溃了怎么办?因为视觉输出会不一致,我们无法继续进行。渲染单个帖子的崩溃不应该“搞垮”整个信息流或使其进入半破坏状态,从而导致进一步崩溃。我们如何以隔离渲染和获取失败的方式编写代码并保持应用的其余部分运行? 容错对用户界面意味着什么?
- 抽象。 在一个小应用中,我们可以对许多特殊情况进行硬编码以解决上述问题,但往往应用会增长。 我们希望能够重用,fork 及连接部分代码,并集体处理它们。 我们希望在不同人熟悉的部分之间定义明确的界限,并避免过分僵化经常变化的逻辑。 我们如何创建隐藏特定 UI 实现细节的抽象? 随着应用的增长,我们如何避免重新引入我们刚刚解决的问题?
当然,还有很多问题我没有提到。这份清单并非详尽无遗!例如,我没有谈到设计师和工程协作,或调试和测试。也许下次再说吧。
将特定视图库或数据提取库作为解决方案解决问题很诱人。但我希望你假装这些库不存在,并从那个角度再次思考。你将如何解决这些问题?使用个小应用试一试! (我很想在 GitHub 上看到你的实践 - 随时给我发推文回复。)
这些问题的有趣之处在于它们大部分都以任意规模出现。你可以在小型组件中看到它们,比如typeahead或工具提示,也能在 Twitter 和 Facebook 等大型应用中看见。
想想你喜欢使用的应用中的那些不平凡的 UI 元素,并查看此问题列表。你能描述一下开发者进行选择的权衡吗?试试从头开始重新创建类似的行为!
通过在小型应用中试验这些问题而不使用库,我学到了很多关于 UI 工程的知识。建议任何想要深入了解 UI 工程权衡的人都试一试。️
英文原文: