后面再考虑用户界面(UI)
本文翻译自Mobx作者 —— Michel Weststrate,于2019年写的文章(UI AS AN AFTERTHOUGHT)。本文没有什么所谓的技术干货,但是从另一个角度阐述了业务和前端的关系,以及相应的前端应用的设计思路,也是我个人认为对于复杂的前端应用来说,比UI first更好的解决方案。原文链接:michel.codes/blogs/ui-as… 以下为原文翻译:
人们经常问我一个问题:“新的React功能(context、hooks、suspense)如何影响未来(Web)应用程序的构建?它们是否会使状态管理库如Redux或MobX变得过时?”
通过这篇文章,我将尝试一劳永逸地回答这个问题!为了真正理解这个问题,我们需要进行一些基础工作。让我们暂时放下React、Redux和MobX,来回答一个更基本的问题。
什么是Web应用程序?就本文而言:Web应用程序是一种允许客户与您的业务互动的用户界面。关键点在于它是一个用户界面。而不是唯一的。一个好的前端的目标是为客户提供一个愉快、无摩擦的体验,以便与您的业务流程互动。但前端不是业务本身!
作为思维实验,想象一下inbox.google.com停止工作(哦,等等,它将会...😭)。理论上,用户可以拿起电话,致电Google,进行认真,并询问Google员工:请告诉我,您有哪些等待我关注的消息?这种思维实验是了解您的业务是什么的一个好方法。如果客户路过您的办公室,他们会问什么问题?如果办公室即将烧毁,您会尝试保存哪些信息?哪些用户互动最终为您的业务赚钱?
我注意到,在前端开发中,我们经常从相反的角度来处理用户界面:我们从一些原型图开始,然后在几乎任意的位置添加状态,让整个界面变得可交互。基本上,状态和数据是事后的想法,是邪恶的必需品,以使那个美丽的用户界面正常工作。从这一方面来开发不可避免地会导致结论:状态是万恶之源。它是个可怕的东西,使一切最初美丽的东西,变得丑陋和复杂。但这里有一个相反的想法:
状态是所有价值的根源
信息。客户与业务流程的互动是最终唯一能够赚钱的东西。是的,更好的用户界面体验很可能会带来更多的钱。但它本身不是赚钱的方式。
所以,在我看来,我们应该从相反的方向来构建Web应用程序,首先开发客户与我们的系统交互。是什么流程?他需要什么信息?他将发送什么信息?换句话说,让我们首先对我们的问题域进行建模。
为解决这些问题进行开发是不需要UI库的。我们可以用抽象的方式开发交互,对它们进行单元测试,建立对所有这些流程中不同状态的深刻理解。
在这一点上,还不清楚客户使用的工具的性质。Web应用程序?React原生应用程序?作为NPM模块的SDK?CLI?这都不重要!所以:
最初,设计状态、存储、流程,就好像正在构建CLI,而不是Web应用程序。
现在,你可能会想:“难道你没有过度工程吗?为什么我要像我发布一个CLI一样构建我的应用程序?我显然永远不会这样做……你是在开玩笑吗?”
现在,离开这篇博客一会儿,回到你目前正在拖延的项目,并启动你的测试运行器……现在再告诉我一次:你的应用程序是否有CLI?你团队中的每个开发者都有一个CLI(希望如此):测试运行器。它与验证您的业务流程并进行交互。单元测试需要与您的流程进行交互的间接级别越少越好。单元测试是您系统的第二个用户界面。甚至如果应用TDD,也是第一个。
React非常出色,允许单元测试了解组件UI并与之交互(无需浏览器等)。但仍然应该能够在不引入“挂载”、“渲染”(浅渲染还是深渲染?)、“触发事件”、“UI快照”等概念的情况下进行测试。这些都是与业务领域无关的概念,不必将您的逻辑与React绑定在一起。
没有什么比直接调用一组函数来执行业务流程更简单。
因此,在这一点上,您可能已经了解到我一直反对直接用React组件状态代表领域状态的原因。这使得业务流程与UI不必要地复杂化。
如果我要为我的应用程序构建CLI,我可能会使用类似yargs或commander的东西。但这并不意味着因为CLI碰巧是我的UI,我就希望那些库管理我的业务流程的状态。换句话说,我会愿意为了切换到yargs和commander而进行完整的重写。对我来说,React就像一个CLI库,它是一个工具,用于捕获用户输入、触发流程并将业务数据转换为漂亮的输出。它是一个构建用户界面的库,而不是业务流程。
只有在您捕获了客户流程、进行了测试并验证它们之后,实际的UI是什么样子才开始变得重要。无论它由什么技术构建,您会发现自己处于一个非常舒适的位置:当开始构建组件时,它们不需要太多的状态。一些组件将拥有自己的一些状态,因为不是所有的UI状态都与业务流程相关(例如,当前选择、选项卡、路由等所有易失状态)。但是,
大多数组件都是无状态组件
您还会发现测试变得更简单;编写更少的测试来挂载组件、触发事件等。您仍然需要一些测试,以验证是否正确地连接了一切,但无需测试所有可能的组合。
巨大的解耦允许非常快速的UI迭代、A/B测试等。因为一旦领域状态和UI解耦,您就更加自由地重构UI。甚至切换到完全不同的UI库或范例都变得更加简单,因为状态几乎不受影响。这很棒,因为在大多数应用程序中,我看到UI的发展速度远高于实际的业务逻辑。
例如,在Mendix,我们非常成功地使用了上述模型。这种分离已经成为每个人自然遵循的范例。一个例子:用户需要上传Excel表格,然后我们在客户端对其运行一些验证,接下来我们与服务器互动,最后启动一些流程。这样的新功能首先会导致一个新的存储(只是一个普通的JS类),它内聚了每个步骤的内部状态和方法、验证逻辑、与后端的互动。然后,我们将创建单元测试,以验证生成了正确的验证消息,并且在所有状态排列和错误条件下该过程是否正确工作。只有在那一点之后,人们才开始构建UI。选择一个不错的上传组件,为所有步骤构建表单。也许更改原始的验证消息(如果听起来不正确的话)。
在这一点上,您可能也了解到为什么我不喜欢将和后端交互直接混入UI中。比如react-apollo绑定作为与graphQL互动的手段。提交更改或获取数据等后端交互是我的领域存储的责任,而不是UI层。对我来说,React-Apollo到目前为止感觉像是一条太容易导致紧密耦合的捷径。
最后!现在是回到我们最初的问题的时候了:“新的React功能(context、hooks、suspense)如何影响未来(Web)应用程序的构建?它们是否会使状态管理库如Redux或MobX过时?”。
对我来说,答案是:新功能不改变状态管理的游戏规则。上下文和hooks功能并不赋予React新的能力。这些只是相同的技巧,更好地组织、更容易组合且不易出错(显然,我是一个粉丝!)。但是,开箱即用的React只能响应由组件拥有的状态。如果您希望您的领域状态存在于组件树之外,您将需要一个单独的状态管理模式、抽象、架构、库来组织它。
最近的React增加的功能并没有从根本上改变状态管理的任何内容。
换句话说:如果您仅仅是因为引入了上下文和hooks而认为不再需要Redux或MobX:那么您以前也不需要它们。
请注意,有了hooks,现在有更少的理由使用MobX来管理您的本地组件状态。特别是考虑到MobX可观察对象作为组件状态将无法利用Suspense的好处。
谈论Suspense与一般状态管理:我认为它只是证明了关注点分离的合理性:Suspense + React状态非常适合管理所有UI状态,以便可以进行并发渲染等操作。但对于我的业务流程呢?业务流程应该始终处于一种状态。
因此,通过这一点,我们希望回答了有关现代React与状态管理的问题:
React应该管理易失的UI状态,而不是您的业务流程。出于这个原因,没有什么真正改变。
最后,简要提一下MobX和mobx-state-tree,您现在可能更好地理解它们的根本目标。它们被设计成:
- 能够独立于任何UI抽象来管理状态。
- 将它们管理的状态与UI巧妙而透明地连接起来。
- 避免管理订阅、选择器和其他手动优化的错误业务,以确保事件不会导致太多的组件重新渲染。
如果您想知道与分开组织的领域状态一起工作有多酷,请观看我的“复杂性:分而治之”演讲,或阅读“如何分离状态和UI”。Olufemi Adeojo最近也写了一篇关于这个问题的文章:“可重用状态管理的好奇案例”。
在我们结束之前,让我们进行一点元分析:每个博主都知道,博客需要图像来吸引用户。这篇博客还没有任何图片,因此界面非常差,效果不佳。但仍然可以执行所有的“业务目标”:与您分享以上的思考。因为,尽管非常重要,但从实现的角度来看: